iOS 基础速览 (辅)
这是辅助篇, 目标不是把你培养成 iOS 工程师, 而是让你在面试里接得住 iOS 相关提问, 展示 “跨端潜力”.重点是用 Android 概念类比, 快速建立对照.
一, Swift 语言基础
- 变量:
let(常量, 类比 Kotlin val),var(变量).类型推断. - 可选值 (Optional):
var name: String?, 概念等同 Kotlin 可空类型.- 可选绑定
if let x = name { }/guard let x = name else { return }. - 强制解包
name!(类比 Kotlin!!). - 可选链
name?.count(类比?.). - 空合运算
name ?? "default"(类比 Elvis?:).
- 可选绑定
- 闭包 (Closure): 类比 Kotlin Lambda,
{ (x: Int) -> Int in x * 2 }, 尾随闭包语法. - 协议 (Protocol): 类比接口, 但更强大 (可扩展, 面向协议编程 POP).
- 结构体 vs 类:
struct是值语义,class是引用语义. 实际存储位置由编译器根据逃逸分析和运行时优化决定, 不能简化成 “struct 一定在栈, class 一定在堆”. - 枚举: 带关联值的枚举很强大, 类比 Kotlin sealed class.
二, 内存管理 ARC
- ARC(Automatic Reference Counting): 编译期自动插入 retain/release, 不是 GC(无运行时回收线程).引用计数为 0 即释放.
- 循环引用: 两个对象互相强引用导致无法释放 (类比内存泄漏).
- 解决:
weak(弱引用, 可为 nil),unowned(非持有引用, 常用于生命周期确定的关系).闭包捕获用[weak self](类比避免 Handler 持有 Activity). - 与 Android 对比: Android 用 GC 可达性分析自动回收; iOS 用 ARC 引用计数, 开发者需主动打破循环引用.
三, UI 体系
- UIViewController 生命周期:
viewDidLoad(类比 onCreate)→viewWillAppear→viewDidAppear→viewWillDisappear→viewDidDisappear. - UIKit: 命令式 UI (类比 View 体系), 用 Storyboard/XIB 或纯代码.
- SwiftUI: 声明式 UI (类比 Jetpack Compose),
@State/@Binding/@ObservedObject管理状态, 概念和 Compose 高度相似: 你学了 Compose 就好理解.
最小 iOS 页面, 权限与沙盒认知
以下是上下文片段, 放在 UIKit 的 UIViewController 子类中; 它不是完整 App 工程. viewDidLoad 只表示 View 已加载, 不等于页面已经可见; 网络请求, 埋点和动画应按实际可见性分别放在合适的生命周期节点.
final class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 页面已出现在前台;适合开始需要可见页面的工作.
}
}
应用级前后台事件由 AppDelegate/SceneDelegate(或 SwiftUI 的 scenePhase) 接收, 不能只依赖单个 ViewController. 权限也不是 “声明后即可访问”:先在 Info.plist 写清用途说明, 再在用户触发操作时请求系统授权, 并处理拒绝, 受限和到设置页恢复后的状态. 沙盒目录按数据语义选择: 用户创建且不可重建的文档放 Documents; 内部持久数据放 Library/Application Support; 可重建缓存放 Library/Caches; 临时文件放 tmp, 后两者均可能被系统清理. 是否备份和是否在 Files 中可见并不会由目录自动保证, 应按业务, 备份排除配置和文件共享配置分别验证. 不要把 token, 密钥或敏感明文放进 UserDefaults, 应使用 Keychain.
ARC 循环引用的可观察例子
以下是可在 Xcode Playground 或 App 工程运行的示例: driver 创建对象, 建立双向连接后将两个外部变量置空. 强引用版不会打印 deinit; 弱引用版预期打印两个 deinit. 该结果只用于说明引用关系, 不代表已在本仓库执行.
final class Owner {
var child: Child?
deinit { print("Owner released") }
}
final class Child {
weak var owner: Owner? // 改成 `var owner: Owner?` 会形成强引用环
deinit { print("Child released") }
}
func makeAndReleasePair() {
var owner: Owner? = Owner()
var child: Child? = Child()
owner!.child = child
child!.owner = owner
owner = nil
child = nil
}
makeAndReleasePair() // weak 版:预期打印 Owner released,Child released(顺序不作为契约)
将 weak var owner 改为强引用 var owner: Owner? 后再运行, 预期不打印任何 deinit, 因为二者互相持有. 排查泄漏时, 症状可能是退出页面后内存持续增长; 证据是 Xcode Memory Graph 中页面, 闭包或 delegate 仍被持有; 定位强引用边, 再按所有权关系选择 weak 或在任务结束时清理闭包. unowned 的重点是不持有对象; 它适用于生命周期关系确定的场景, 若访问已释放对象会崩溃. 常见写法是非可选 unowned, 但 Swift 也支持 optional unowned, 不能据此把它绝对描述为 “不为 nil”.
本篇只建立 iOS 原生基础对照; 共享 Kotlin 代码的工程配置见 Kotlin Multiplatform, 框架取舍见 跨端技术对比.
四, 并发 GCD
- GCD(Grand Central Dispatch): iOS 的并发框架, 基于队列.
- 主队列:
DispatchQueue.main, UI 操作 (类比 Android 主线程 / Dispatchers.Main). - 全局队列:
DispatchQueue.global(qos:), 后台任务 (类比 Dispatchers.IO/Default). - 异步 / 同步:
async(不阻塞, 类比 launch),sync(阻塞当前). - 现代 Swift Concurrency 使用
async/await,Task, actor 和Sendable. 可与 Kotlin 协程类比建立直觉, 但取消, actor 隔离和错误传播规则不能机械等同.
DispatchQueue.global().async { // 后台
let data = loadData()
DispatchQueue.main.async { // 回主线程更新 UI
self.label.text = data
}
}
五, Android / iOS 概念对照表
| 概念 | Android | iOS |
|---|---|---|
| 页面 | Activity | UIViewController |
| 列表 | RecyclerView | UITableView/UICollectionView |
| 声明式 UI | Jetpack Compose | SwiftUI |
| 异步主线程切换 | Handler/协程 | GCD/async-await |
| 内存管理 | GC (可达性分析) | ARC (引用计数) |
| 空安全 | Kotlin 可空类型 | Optional |
| 接口 | interface | protocol |
| 依赖管理 | Gradle | CocoaPods/SPM |
| 本地存储 | SharedPreferences/DataStore | UserDefaults |
| 包格式 | APK/AAB | IPA |
高频面试题 (iOS 通常只是辅助考察)
Q1: iOS 的内存管理和 Android 有什么不同? iOS 用 ARC, 编译期插入 retain/release 做引用计数, 计数为 0 即释放, 无运行时 GC 线程, 需手动用 weak/unowned 打破循环引用. Android 用 GC 可达性分析在运行时回收, 无需手动管理引用计数.
Q2: weak 和 unowned 区别?
都不增加引用计数. weak 引用对象释放后变 nil (通常声明为可选); unowned 假设对象在引用期间一直存在, 访问已释放对象会崩溃. unowned 常见为非可选, 但 Swift 也支持 optional unowned; 生命周期不确定用 weak, 能严格证明被引用对象活得更久时再用 unowned.
Q3: struct 和 class 区别?
struct 提供值语义, 赋值和传参在语义上产生独立值, 但编译器可以用 copy-on-write, 逃逸分析等方式优化; class 提供对象身份和共享引用, 由 ARC 管理. 值语义有助于减少无意共享, 但如果字段包含可变引用或跨并发域传递不满足 Sendable / 隔离规则, 并不会自动线程安全.
Q4: SwiftUI 和 Compose 像吗? 相似. 都是声明式, 状态驱动, UI=f (state), 单向数据流.@State 类比 remember+mutableStateOf,@Binding 类比状态提升. 理解了 Compose 基本能迁移过去.
Q5: 你做过 iOS 吗? 能转吗?(开放题) 诚实说做过部分 iOS, 理解 Swift/UIKit/ARC 基础, 有跨端经验; 强调底层 (C/C++) 和概念是相通的, 跨端学习成本可控. 不要夸大成 “精通”.