Appearance
01 - iOS 应用核心深度
目录
1. 应用生命周期
iOS 应用生命周期深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 生命周期流程: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 1. main() 函数 │
│ │ 2. UIApplicationMain() 创建 UIApplication │
│ │ 3. delegate 初始化 │
│ │ 4. 创建 Scene / UIWindow │
│ │ 5. 设置 rootViewController │
│ │ 6. 进入 RunLoop │
│ │ │
│ │ 生命周期方法(UIApplication): │
│ │ • application:didFinishLaunchingWithOptions: │
│ │ → App 启动完成,可进行初始化 │
│ │ • applicationDidBecomeActive: │
│ │ → App 成为前台活跃状态 │
│ │ • applicationWillResignActive: │
│ │ → App 即将进入后台 │
│ │ • applicationDidEnterBackground: │
│ │ → App 进入后台 │
│ │ • applicationWillEnterForeground: │
│ │ → App 即将进入前台 │
│ │ • applicationWillTerminate: │
│ │ → App 即将终止 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ SceneDelegate 生命周期(iOS 13+): │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ • scene:willConnectToSession:options: │
│ │ → Scene 连接到 session │
│ │ • sceneDidDisconnect: │
│ │ → Scene 断开连接 │
│ │ • sceneDidBecomeActive: │
│ │ → Scene 变为活跃状态 │
│ │ • sceneWillResignActive: │
│ │ → Scene 即将失去焦点 │
│ │ • sceneWillEnterForeground: │
│ │ → Scene 即将进入前台 │
│ │ • sceneDidEnterBackground: │
│ │ → Scene 进入后台 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 生命周期对比(UIKit vs SwiftUI): │
│ ┌──────────────┬────────────────┬──────────────────┐ │ │
│ │ 阶段 │ UIKit │ SwiftUI │ │
│ ├──────────────┼────────────────┼──────────────────┤ │
│ │ 启动 │ AppDelegate │ @main App struct │ │
│ │ 场景连接 │ SceneDelegate │ @main App struct │ │
│ │ 视图加载 │ viewDidLoad │ onAppear │ │
│ │ 视图显示 │ viewWillAppear │ — │ │
│ │ 视图消失 │ viewWillDisappear │ onDisappear │ │
│ │ 内存警告 │ didReceiveMemoryWarning │ onDisappear │ │
│ └──────────────┴────────────────┴──────────────────┘ │
│ │
│ 生命周期注意事项: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ • AppDelegate 和 SceneDelegate 共存 │
│ │ • iOS 13+ 推荐使用 SceneDelegate │
│ │ • UIKit 应用也可使用 SceneDelegate │
│ │ • SwiftUI 应用使用 @main App struct │
│ │ • 生命周期方法中避免耗时操作 │
│ │ • 后台模式需要配置 Entitlements │
│ │ • 应用终止前需要保存数据 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/2. 启动流程深度分析
启动流程深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 启动流程(详细): │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 阶段 1: dyld(动态链接器) │
│ │ • 加载 Mach-O 文件 │
│ │ • 链接依赖库(@rpath, @executable_path, @loader_path) │
│ │ • 解析符号(函数、变量) │
│ │ • 初始化(+load 方法调用) │
│ │ │
│ │ 阶段 2: +load 初始化(静态) │
│ │ • 所有类的 +load 方法按编译顺序调用 │
│ │ • 所有分类的 +load 方法按编译顺序调用 │
│ │ • 在 main() 之前调用 │
│ │ • 不能延迟(静态初始化) │
│ │ │
│ │ 阶段 3: main() 函数 │
│ │ • 调用 UIApplicationMain() │
│ │ • 创建 UIApplication 和 delegate │
│ │ • 设置主 Window 和 rootViewController │
│ │ │
│ │ 阶段 4: UIApplicationMain() │
│ │ • 创建 UIApplication 实例 │
│ │ • 加载 Info.plist │
│ │ • 创建 Scene / UIWindow │
│ │ • 设置 delegate │
│ │ • 启动 RunLoop │
│ │ │
│ │ 阶段 5: RunLoop 启动 │
│ │ • 进入事件循环 │
│ │ • 监听 Source(输入源) │
│ │ • 处理 Timer(定时器) │
│ │ • 处理 GCD 任务 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ dyld 动态链接详解: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // dyld 工作流程 │
│ │ 1. dyld 加载主程序 Mach-O 文件 │
│ │ 2. 递归加载依赖库(@rpath, @executable_path, @loader_path) │
│ │ 3. 解析未解析的符号 │
│ │ 4. 调用 +load 方法(每个类和分类) │
│ │ 5. 调用 +initialize 方法(懒加载) │
│ │ 6. 跳转到 main() │
│ │ │
│ │ 依赖路径优先级: │
│ │ • @executable_path → 应用可执行文件路径 │
│ │ • @loader_path → 加载者路径 │
│ │ • @rpath → 运行时路径搜索 │
│ │ • @provides → 提供库的符号 │
│ │ • @weak_framework → 弱依赖库 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ +load vs +initialize 对比: │
│ ┌──────────────┬──────────────────┬──────────────────┐ │
│ │ 特性 │ +load │ +initialize │ │
│ ├──────────────┼──────────────────┼──────────────────┤ │
│ │ 调用时机 │ dyld 加载时 │ 首次使用类时 │ │
│ │ 线程安全 │ ✅ 安全 │ ✅ 安全(dispatch_once)│ │
│ │ 调用顺序 │ 按编译顺序 │ 首次使用 │ │
│ │ 延迟初始化 │ ❌ 不延迟 │ ✅ 可延迟 │ │
│ │ 推荐用途 │ 静态初始化 │ 懒加载初始化 │ │
│ └──────────────┴──────────────────┴──────────────────┘ │
│ │
│ 启动优化策略: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ • 减少 dyld 链接时间(合并 framework、减少动态库) │
│ │ • 减少 +load 方法(避免静态初始化) │
│ │ • 延迟初始化(非关键功能延迟到 main 之后) │
│ │ • 预编译头(PCH)减少编译时间 │
│ │ • 主线程异步化(减少主线程工作) │
│ │ • 使用 LaunchScreen 减少首屏加载时间 │
│ │ • 减少 -init 初始化函数 │
│ │ • 图片懒加载 │
│ │ • JSON 预编译 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 启动时间测量: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ • Instruments(Time Profiler) │
│ │ • NSLog 手动计时 │
│ │ • DYLD_STATS 环境变量 │
│ │ • NSTimeInterval 计时 │
│ │ │
│ │ 冷启动时间分解: │
│ │ • dyld 加载 + 链接:~300ms │
│ │ • +load 初始化:~50ms │
│ │ • main() + UIApplicationMain:~100ms │
│ │ • Scene/Window 创建:~50ms │
│ │ • rootViewController 加载:~100ms │
│ │ • 首屏渲染 + 数据加载:~300ms │
│ │ ───────────────────────────────────────────────────── │
│ │ 总冷启动时间:~900ms(理想)→ 2-5s(实际) │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/3. 运行循环 RunLoop
RunLoop 深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ RunLoop 的核心: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ RunLoop 的本质: │
│ │ • 事件处理循环(Event Loop) │
│ │ • 管理事件源(Source)、定时器(Timer)、观察者(Observer) │
│ │ • 保持线程存活 │
│ │ • 自动管理内存(AutoreleasePool) │
│ │ │
│ │ RunLoop 结构: │
│ │ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ CFRunLoopRef │ │
│ │ │ ├── CFRunLoopMode │ │
│ │ │ │ ├── CFSocketPort(网络事件) │ │
│ │ │ │ ├── GCD(DispatchSource) │ │
│ │ │ │ ├── Timer(NSTimer) │ │
│ │ │ │ └── Observer(输入源/定时器/端口) │ │
│ │ │ └── CFRunLoopMode │ │
│ │ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ │ RunLoop 模式(Modes): │
│ │ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ • NSDefaultRunLoopMode(默认模式) │
│ │ │ • NSRunLoopCommonModes(常用模式,包含所有模式) │
│ │ │ • UITrackingRunLoopMode(触摸跟踪模式) │
│ │ │ • NSRunLoopInputMode(输入模式) │
│ │ │ • NSRunLoopModalMode(模态模式) │
│ │ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ │ RunLoop 工作流程: │
│ │ ┌────────────────────────────────────────────────────────┐ │
│ │ │ 1. 准备(prepare) │
│ │ │ 2. 处理源(handle sources) │
│ │ │ 3. 处理定时器(handle timers) │
│ │ │ 4. 处理观察者(handle observers) │
│ │ │ 5. 休眠(wait) │
│ │ │ 6. 唤醒(wakeup) │
│ │ │ 7. 通知(notify) │
│ │ │ 8. 退出(exit) │
│ │ └────────────────────────────────────────────────────────┘ │
│ │ │
│ │ RunLoop 应用场景: │
│ │ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ • 懒加载(延迟初始化) │
│ │ │ • 自动释放池(内存管理) │
│ │ │ • 保持线程存活 │
│ │ │ • 异步任务(GCD + RunLoop) │
│ │ │ • Timer 管理 │
│ │ │ • 网络事件处理 │
│ │ │ • 触摸事件处理 │
│ │ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ │ RunLoop 与 Timer 的关系: │
│ │ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ • Timer 依赖 RunLoop 模式 │
│ │ │ • Timer 在 NSDefaultRunLoopMode 下工作 │
│ │ │ • 当用户滚动时,RunLoop 切换到 UITrackingRunLoopMode │
│ │ │ • Timer 在 UITrackingRunLoopMode 下暂停 │
│ │ │ • 解决方法:将 Timer 添加到 NSRunLoopCommonModes │
│ │ │ │
│ │ │ // 解决方法: │
│ │ │ let timer = Timer(timeInterval: 1.0, target: self, │
│ │ │ selector: #selector(timerFired), userInfo: nil, │
│ │ │ repeats: true) │
│ │ │ RunLoop.current.add(timer, forMode: .commonModes) │
│ │ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ │ RunLoop 性能分析: │
│ │ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ • 主线程 RunLoop 持续运行 │
│ │ │ • 子线程需手动管理 │
│ │ │ • 主线程卡顿:RunLoop 被阻塞 │
│ │ │ • 主线程卡顿检测:CADisableMinimumFrameDuration │
│ │ │ • 主线程卡顿修复:减少主线程工作量 │
│ │ │ • 主线程卡顿修复:异步处理 │
│ │ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 总结: │
│ │ • RunLoop 是事件循环的核心机制 │
│ │ • 管理事件源、定时器、观察者 │
│ │ • 保持线程存活 │
│ │ • 自动管理内存(AutoreleasePool) │
│ │ • 主线程 RunLoop 持续运行 │
│ │ • Timer 依赖 RunLoop 模式 │
│ │ • 主线程卡顿:RunLoop 被阻塞 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ RunLoop 与 GCD 的关系: │
│ ┌──────────────┬──────────────────┬──────────────────┐ │
│ │ 特性 │ RunLoop │ GCD │ │
│ ├──────────────┼──────────────────┼──────────────────┤ │
│ │ 本质 │ 事件循环 │ 任务调度 │ │
│ │ 管理 │ 事件源/定时器/观察者 │ 队列/线程池 │ │
│ │ 线程 │ 手动管理 │ 自动管理 │ │
│ │ 模式 │ Mode(模式) │ Priority(优先级) │ │
│ │ 适用 │ 事件处理、Timer │ 并发任务、异步处理 │ │
│ └──────────────┴──────────────────┴──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/4. 沙盒机制
沙盒机制深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ iOS 沙盒结构: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ /Applications/ │
│ │ ├── YourApp.app/ │
│ │ │ ├── Info.plist │
│ │ │ ├── YourApp(可执行文件) │
│ │ │ ├── Resources/(资源文件) │
│ │ │ └── Frameworks/(动态库) │
│ │ └── ... │
│ │ │
│ │ /private/var/containers/Bundle/Application/ │
│ │ └── YourApp.app/ │
│ │ │
│ │ /private/var/mobile/Containers/Data/Application/ │
│ │ ├── Documents/(用户数据,iTunes 同步) │
│ │ ├── Library/ │
│ │ │ ├── Caches/(缓存数据,可清除) │
│ │ │ ├── Preferences/(UserDefaults) │
│ │ │ └── Application Support/(应用数据) │
│ │ └── tmp/(临时文件,可被系统清理) │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 沙盒权限: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ • 应用只能访问自己的沙盒目录 │
│ │ • 不能访问其他应用的沙盒 │
│ │ • 不能访问系统目录 │
│ │ • 文件权限:rwx------(应用所有者权限) │
│ │ • 目录权限:rwxr-xr-x(其他应用只读) │
│ │ • 网络权限:需要通过 Entitlements 配置 │
│ │ • 设备权限:需要通过 Entitlements 配置 │
│ │ • iCloud 权限:需要通过 Entitlements 配置 │
│ │ │
│ │ 目录用途总结: │
│ │ • Documents:用户数据(需备份) │
│ │ ├── Library/Caches:缓存数据(可清除) │
│ │ ├── Library/Preferences:用户偏好设置 │
│ │ └── tmp:临时文件(可被系统清理) │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 沙盒最佳实践: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ • 使用 FileManager.default 获取路径 │
│ │ • 使用 UserDefaults 存储偏好设置 │
│ │ • 使用 CoreData/SQLite 存储结构化数据 │
│ │ • 使用 Keychain 存储敏感数据 │
│ │ • 使用 FileManager 进行文件操作 │
│ │ • 避免在沙盒外读写数据 │
│ │ • 注意文件权限 │
│ │ • 注意 iCloud 同步 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/5. 通知中心 KVO KVC
通知中心 KVO KVC 深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ NotificationCenter: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // NotificationCenter 使用 │
│ │ // 发送通知 │
│ │ NotificationCenter.default.post(name: .someNotification, │
│ │ object: self, userInfo: ["key": "value"]) │
│ │ │
│ │ // 接收通知 │
│ │ NotificationCenter.default.addObserver( │
│ │ self, │
│ │ selector: #selector(handleNotification), │
│ │ name: .someNotification, │
│ │ object: nil │
│ │ ) │
│ │ │
│ │ // 移除观察者 │
│ │ NotificationCenter.default.removeObserver(self) │
│ │ │
│ │ NotificationCenter 原理: │
│ │ • 观察者模式实现 │
│ │ • 基于 NSHashTable(弱引用) │
│ │ • 线程安全(NSLock) │
│ │ • 观察者按添加顺序通知 │
│ │ • 通知对象可以为 nil(广播模式) │
│ │ • userInfo 用于传递额外信息 │
│ │ │
│ │ NotificationCenter 优缺点: │
│ │ ┌──────────────┬──────────────────┬──────────────────┐ │
│ │ │ 优点 │ 缺点 │ │ │
│ │ ├──────────────┼──────────────────┼──────────────────┤ │
│ │ │ 解耦 │ 内存泄漏风险 │ │ │
│ │ │ 灵活 │ 类型不安全 │ │ │
│ │ │ 广播 │ 调试困难 │ │ │
│ │ │ 解耦 │ 内存泄漏风险 │ │ │
│ │ │ 灵活 │ 类型不安全 │ │ │
│ │ │ 广播 │ 调试困难 │ │ │
│ │ │ 解耦 │ 内存泄漏风险 │ │ │
│ │ │ 灵活 │ 类型不安全 │ │ │
│ │ │ 广播 │ 调试困难 │ │ │
│ │ └──────────────┴──────────────────┴──────────────────┘ │
│ │ │
│ │ NotificationCenter 使用注意: │
│ │ • 观察者模式实现 │
│ │ • 基于 NSHashTable(弱引用) │
│ │ • 线程安全(NSLock) │
│ │ • 观察者按添加顺序通知 │
│ │ • 通知对象可以为 nil(广播模式) │
│ │ • userInfo 用于传递额外信息 │
│ │ • 移除观察者(防止内存泄漏) │
│ │ • 使用 Notifications 代替(类型安全) │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ KVO(Key-Value Observing): │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // KVO 原理 │
│ │ • 动态替换 isa 指针 │
│ │ • 创建 NSKeyValueObserver 子类 │
│ │ • 拦截 valueForKey/setValueForKey 调用 │
│ │ • 通知观察者 │
│ │ │
│ │ // KVO 使用 │
│ │ object.addObserver(self, forKeyPath: "propertyName", │
│ │ options: .new, context: nil) │
│ │ │
│ │ // KVO 注意事项 │
│ │ • 移除观察者(防止内存泄漏) │
│ │ • 使用 Swift KeyPath(类型安全) │
│ │ • 使用 Combine(现代替代方案) │
│ │ • 使用 KVO 替代方案(Delegate/Callback) │
│ │ │
│ │ KVO vs NotificationCenter: │
│ │ ┌──────────────┬──────────────────┬──────────────────┐ │
│ │ │ 特性 │ KVO │ NotificationCenter │ │
│ │ ├──────────────┼──────────────────┼──────────────────┤ │
│ │ │ 解耦 │ 低 │ 高 │ │
│ │ │ 类型安全 │ 高 │ 低 │ │
│ │ │ 适用 │ 属性变化 │ 事件通知 │ │
│ │ │ 性能 │ 中 │ 高 │ │
│ │ └──────────────┴──────────────────┴──────────────────┘ │
│ │ │
│ │ KVC(Key-Value Coding): │
│ │ ┌──────────────┬──────────────────┬──────────────────┐ │
│ │ │ 特性 │ KVC │ │ │
│ │ ├──────────────┼──────────────────┼──────────────────┤ │
│ │ │ 本质 │ 动态键值访问 │ │ │
│ │ │ 原理 │ NSDictionary/NSMutableDictionary 映射 │ │
│ │ │ 使用 │ valueForKey/setValueForKey │ │
│ │ │ 类型安全 │ 低(字符串键) │ │ │
│ │ │ 适用 │ 动态数据绑定 │ │ │
│ │ └──────────────┴──────────────────┴──────────────────┘ │
│ │ │
│ │ KVC 使用: │
│ │ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ // 获取值 │
│ │ │ let value = object.value(forKey: "propertyName") as? String │
│ │ │ │
│ │ │ // 设置值 │
│ │ │ object.setValue("new value", forKey: "propertyName") │
│ │ │ │
│ │ │ // KVC 注意事项 │
│ │ │ • 键路径不存在时调用 setValue:forUndefinedKey:(可覆盖) │
│ │ │ • 线程安全:需手动管理 │
│ │ │ • 类型安全:低(字符串键) │
│ │ │ • 推荐使用 Combine/SwiftUI 替代 │
│ │ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/6. 代理模式与闭包回调
代理模式与闭包回调深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ 代理模式: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // 代理模式本质: │
│ │ • 协议驱动的设计模式 │
│ │ • 委托方持有代理(weak) │
│ │ • 代理实现协议方法 │
│ │ • 委托方通过代理方法通知代理 │
│ │ │
│ │ 代理模式实现: │
│ │ ┌──────────────────────────────────────────────────────────┐ │
│ │ │ protocol MyDelegate: AnyObject { │
│ │ │ func didChange(value: Int) │
│ │ │ } │
│ │ │ │
│ │ │ class MyClass { │
│ │ │ weak var delegate: MyDelegate? │
│ │ │ │
│ │ │ func doSomething() { │
│ │ │ // ... │
│ │ │ delegate?.didChange(value: newValue) │
│ │ │ } │
│ │ │ } │
│ │ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 代理模式 vs 闭包回调: │
│ │ ┌──────────────┬──────────────────┬──────────────────┐ │
│ │ │ 特性 │ 代理模式 │ 闭包回调 │ │
│ │ ├──────────────┼──────────────────┼──────────────────┤ │
│ │ │ 解耦 │ 高 │ 中 │ │
│ │ │ 类型安全 │ 高(协议) │ 中(泛型) │ │
│ │ │ 适用 │ 多方法、多事件 │ 单事件、简单回调 │ │
│ │ │ 生命周期 │ 需手动管理 │ 可选强/弱捕获 │ │
│ │ │ 性能 │ 中 │ 高 │ │
│ │ │ 推荐 │ 多事件 │ 单事件 │ │
│ │ └──────────────┴──────────────────┴──────────────────┘ │
│ │ │
│ │ 代理模式最佳实践: │
│ │ • 代理协议使用 weak 修饰 │
│ │ • 代理协议名称以 Delegate 结尾 │
│ │ • 代理方法使用 @objc optional 可选方法 │
│ │ • 代理方法命名以 did/will 开头 │
│ │ • 代理方法参数使用有意义的名称 │
│ │ • 使用委托模式代替继承 │
│ │ • 使用协议扩展提供默认实现 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 闭包回调: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // 闭包回调实现 │
│ │ class MyClass { │
│ │ func doSomething(completion: @escaping (Result<Data, Error>) -> Void) { │
│ │ // ... │
│ │ completion(.success(data)) │
│ │ } │
│ │ } │
│ │ │
│ │ 闭包回调最佳实践: │
│ │ • 使用 @escaping 修饰闭包 │
│ │ • 使用 Result 包装返回值 │
│ │ • 使用 [weak self] 捕获 self │
│ │ • 使用 MainActor.run 在主线程回调 │
│ │ • 使用 async/await 替代闭包(现代 Swift) │
│ │ • 避免内存泄漏(正确捕获 self) │
│ │ • 使用 CompletionHandler 命名(可选) │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/7. 单例模式
单例模式深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ 单例模式实现: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // 1. Swift 静态实例(推荐 ✅) │
│ │ final class Singleton { │
│ │ static let shared = Singleton() │
│ │ private init() { } │
│ │ } │
│ │ │
│ │ // 2. Objective-C 实现 │
│ │ @interface Singleton : NSObject │
│ │ @property (nonatomic, class, readonly) Singleton *shared; │
│ │ @end │
│ │ @implementation Singleton │
│ │ + (instancetype)shared { │
│ │ static Singleton *_shared = nil; │
│ │ static dispatch_once_t onceToken; │
│ │ dispatch_once(&onceToken, ^{ │
│ │ _shared = [[Singleton alloc] init]; │
│ │ }); │
│ │ return _shared; │
│ │ } │
│ │ @end │
│ │ │
│ │ // 3. Swift 线程安全单例(GCD) │
│ │ class Singleton { │
│ │ static let shared: Singleton = { │
│ │ let instance = Singleton() │
│ │ return instance │
│ │ }() │
│ │ } │
│ │ │
│ │ 单例注意事项: │
│ │ • 使用 final 防止继承 │
│ │ • init 设为 private │
│ │ • 使用 class 而非 static(避免循环依赖) │
│ │ • 内存泄漏:单例持有 strong 循环 │
│ │ • 线程安全:dispatch_once 或 GCD │
│ │ • 内存泄漏:单例持有 strong 循环 │
│ │ • 线程安全:dispatch_once 或 GCD │
│ │ │
│ │ 单例优缺点: │
│ │ ┌──────────────┬──────────────────┬──────────────────┐ │
│ │ │ 优点 │ 缺点 │ │ │
│ │ ├──────────────┼──────────────────┼──────────────────┤ │
│ │ │ 全局唯一 │ 内存泄漏风险 │ │ │
│ │ │ 线程安全 │ 难以测试 │ │ │
│ │ │ 访问快速 │ 单点故障 │ │ │
│ │ │ 简化访问 │ 依赖隐式 │ │ │
│ │ └──────────────┴──────────────────┴──────────────────┘ │
│ │ │
│ │ 单例使用场景: │
│ │ • 全局配置管理器 │
│ │ • 网络请求管理器 │
│ │ • 缓存管理器 │
│ │ • 数据库管理器 │
│ │ • 用户管理器 │
│ │ • 状态管理器 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/8. 后台模式
后台模式深度分析:
┌─────────────────────────────────────────────────────────────────┐
│ iOS 后台模式: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 后台模式类型: │
│ │ • BGTaskScheduler(后台任务调度器) │
│ │ • BGAppRefreshTask(App 刷新任务) │
│ │ • BGMaintenanceTask(维护任务) │
│ │ • BGProcessingTask(处理任务) │
│ │ • backgroundFetch(后台拉取) │
│ │ • remoteNotification(远程通知) │
│ │ • audio(音频播放) │
│ │ • location(位置服务) │
│ │ • voip(VoIP) │
│ │ • externalAccessory(外部配件) │
│ │ │
│ │ 后台任务注册: │
│ │ ┌──────────────────────────────────────────────────────────┐ │
│ │ │ BGTaskScheduler.shared.register( │
│ │ │ forTaskWithIdentifier: "com.app.refresh", │
│ │ │ using: nil) { task in │
│ │ │ self.handleAppRefresh(task: task as! BGAppRefreshTask) │
│ │ │ } │
│ │ │ │
│ │ │ // 调度后台任务 │
│ │ │ let task = BGAppRefreshTask(identifier: "com.app.refresh") │
│ │ │ task.expirationHandler = { │
│ │ │ // 任务即将过期 │
│ │ │ } │
│ │ │ BGTaskScheduler.shared.submit(task) │
│ │ │ │
│ │ │ // 后台任务执行 │
│ │ │ func handleAppRefresh(task: BGAppRefreshTask) { │
│ │ │ // 执行后台任务 │
│ │ │ task.setTaskCompleted(success: true) │
│ │ │ } │
│ │ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 后台模式配置: │
│ │ • Info.plist 添加 UIBackgroundModes │
│ │ • Entitlements 配置后台权限 │
│ │ • 后台任务需要用户授权 │
│ │ • 后台任务有执行时间限制(约 30s) │
│ │ • 后台任务需要合理的调度频率 │
│ │ • 后台任务需要处理网络请求 │
│ │ • 后台任务需要处理数据库操作 │
│ │ • 后台任务需要处理文件操作 │
│ │ • 后台任务需要处理图片加载 │
│ │ • 后台任务需要处理视频处理 │
│ │ │
│ │ 后台模式最佳实践: │
│ │ • 使用 BGTaskScheduler 注册任务 │
│ │ • 使用 BGAppRefreshTask 进行 App 刷新 │
│ │ • 使用 BGMaintenanceTask 进行维护任务 │
│ │ • 使用 BGProcessingTask 进行长时间任务 │
│ │ • 使用 backgroundFetch 进行后台拉取 │
│ │ • 使用 remoteNotification 进行远程通知 │
│ │ • 使用 audio 进行音频播放 │
│ │ • 使用 location 进行位置服务 │
│ │ • 使用 voip 进行 VoIP 通话 │
│ │ • 使用 externalAccessory 进行外部配件 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/9. 面试考点汇总
高频面试题
Q1: iOS 应用生命周期是怎样的?
答:
- 启动流程:main() → UIApplicationMain() → delegate → Scene → UIWindow → rootVC
- 生命周期方法:didFinishLaunching → didBecomeActive → applicationDidEnterBackground
- iOS 13+ 使用 SceneDelegate 管理场景
Q2: 启动流程中的 dyld 作用?
答:
- 加载 Mach-O 文件、链接依赖库、解析符号
- 调用 +load 方法(静态初始化)
- 调用 +initialize 方法(懒加载)
- 优化:减少动态库、预编译头、延迟初始化
Q3: RunLoop 的核心作用?
答:
- 事件处理循环(Event Loop)
- 管理事件源、定时器、观察者
- 保持线程存活
- 自动管理内存(AutoreleasePool)
- 主线程 RunLoop 持续运行
Q4: 沙盒机制是什么?
答:
- 每个应用有独立的文件系统
- 不能访问其他应用的沙盒
- 目录:Documents、Library、tmp
- 文件权限:rwx------(应用所有者)
Q5: 单例模式的实现?
答:
- Swift:static let shared = Singleton()
- Objective-C:dispatch_once 线程安全
- 注意事项:final、private init、线程安全
- 单例使用场景:全局配置、网络管理器、缓存管理器
Q6: 后台模式有哪些?
答:
- BGTaskScheduler(后台任务调度器)
- BGAppRefreshTask(App 刷新)
- BGMaintenanceTask(维护任务)
- BGProcessingTask(长时间任务)
- backgroundFetch(后台拉取)
- remoteNotification(远程通知)
2.5 启动流程底层原理深度剖析
2.5.1 dyld 启动链完整分析
dyld 启动链完整流程(iOS 17+):
┌─────────────────────────────────────────────────────────────────────┐
│ Level 0: dyld 入口 │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ __dyld_start (汇编入口点) │
│ │ ├── 创建初始栈帧 │
│ │ ├── 保存 argc/argv/environp │
│ │ └── 跳转到 dyldbootstrap::start │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ Level 1: dyldbootstrap 初始化 │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ dyldbootstrap::start() │
│ │ ├── getExecutablePath() → 获取可执行文件路径 │
│ │ ├── _mh_execute_header → 获取 Mach-O header │
│ │ ├── dyld::initializeMainStack() → 重建堆栈 │
│ │ └── dyld::runInitializers() → 调用 __dyld_section │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ Level 2: dyld 主初始化 │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ dyld::initializeMain() │
│ │ ├── loadMainExecutable() → 加载主程序 Mach-O │
│ │ ├── mapSections() → 映射代码段/数据段到内存 │
│ │ ├── bindSymbols() → 绑定符号引用 │
│ │ ├── loadAllDependentLibraries() → 递归加载依赖库 │
│ │ └── callInitializers() → 调用所有 +load 方法 │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ Level 3: Mach-O 加载器 │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ Mach-O 文件格式: │
│ │ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ Magic (0xfeedface/0xfeedfacf) │ │
│ │ │ CPU Type (ARM64) │ │
│ │ │ CPU Subtype │ │
│ │ │ File Type (MH_EXECUTE) │ │
│ │ │ Load Commands Count │ │
│ │ │ Load Commands → [LC_SEGMENT_64, LC_SYMTAB, LC_DYSYMTAB...] │
│ │ │ Text Segment → 代码段 (.text, .text._dispatch) │ │
│ │ │ Data Segment → 数据段 (.data, .cstring) │ │
│ │ │ BSS Segment → 未初始化数据段 │ │
│ │ │ Symbol Table → 符号表 │ │
│ │ │ String Table → 字符串表 │ │
│ │ └────────────────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ Level 4: 依赖库加载顺序 │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ dyld3 的依赖库加载(iOS 12+ 使用 dyld3) │
│ │ ├── dyldA::link() → 动态链接器核心 │
│ │ ├── GraphResolver::resolve() → 符号解析 │
│ │ ├── ImageLoader::link() → 链接单个 Image │
│ │ ├── ImageLoader::bind() → 绑定引用 │
│ │ ├── ImageLoader::postLoad() → 后处理 │
│ │ └── ImageLoader::initialize() → 调用 +initialize │
│ │ │
│ │ 依赖库搜索路径优先级(dyld3): │
│ │ 1. @executable_path → /Applications/YourApp.app/ │
│ │ 2. @loader_path → 当前 Image 的加载路径 │
│ │ 3. @rpath → 运行时路径(从 Info.plist 读取) │
│ │ 4. @framework_path → 系统 Framework 路径 │
│ │ 5. DYLD_FRAMEWORK_PATH → 环境变量 │
│ │ 6. DYLD_LIBRARY_PATH → 库搜索路径 │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ Level 5: Mach-O 加载命令详解 │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ LC_LOAD_DYLIB → 动态链接库 │
│ │ LC_LOAD_UPWARD_DYLIB → 向上加载依赖库 │
│ │ LC_ID_DYLIB → 库身份标识 │
│ │ LC_SEGMENT_64 → 64位段映射 │
│ │ LC_SYMTAB → 符号表 │
│ │ LC_DYSYMTAB → 动态符号表 │
│ │ LC_LOAD_WEAK_DYLIB → 弱链接动态库 │
│ │ LC_CODE_SIGNATURE → 代码签名 │
│ │ LC_FUNCTION_STARTS → 函数起始地址 │
│ │ LC_MAIN → main 入口地址(替代 LC_UNIXTHREAD) │
│ │ LC_SOURCE_VERSION → 源码版本 │
│ │ LC_VERSION_MIN_IPHONEOS → iOS 最低版本要求 │
│ │ LC_RPATH → 运行时路径搜索 │
│ │ LC_ENCRYPTION_INFO_64 → 代码加密信息 │
│ │ LC_BUILD_VERSION → 构建版本(toolset/sdk) │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ Level 6: 符号绑定完整流程 │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ 符号绑定阶段: │
│ │ ├── Non-lazy binding → 非懒绑定(函数指针,启动时绑定) │
│ │ ├── Lazy binding → 懒绑定(函数指针,首次调用时绑定) │
│ │ └── Lazy binding stub → 懒绑定桩代码(调用 __dyld_lazy_bind) │
│ │ │
│ │ 符号表结构(LC_SYMTAB): │
│ │ ├── symtab.symoff → 符号表偏移 │
│ │ ├── symtab.nsyms → 符号数量 │
│ │ ├── strtab.stroff → 字符串表偏移 │
│ │ └── strtab.strsize → 字符串表大小 │
│ │ │
│ │ 符号类型(n_type 字段): │
│ │ ├── N_SECT → 段内符号 │
│ │ ├── N_PBUD → 未绑定动态符号 │
│ │ ├── N_ABS → 绝对符号 │
│ │ └── N_INDR → 间接符号 │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ 冷启动 vs 热启动时间对比(iPhone 15 Pro): │
│ ┌───────────┬──────────────┬──────────────┬──────────────┐ │
│ │ 阶段 │ 冷启动(ms) │ 热启动(ms) │ 增量(ms) │ │
│ ├──────────┼──────────────┼──────────────┼──────────────┤ │
│ │ dyld加载 │ 180 │ 12 │ +168 │ │
│ │ 符号链接 │ 120 │ 8 │ +112 │ │
│ │ +load │ 45 │ 3 │ +42 │ │
│ │ +init │ 60 │ 2 │ +58 │ │
│ │ main() │ 30 │ 2 │ +28 │ │
│ │ UI创建 │ 150 │ 20 │ +130 │ │
│ │ 首屏渲染 │ 200 │ 30 │ +170 │ │
│ │ 网络请求 │ 500 │ 400 │ +100 │ │
│ │ ──────── │ ──────────── │ ──────────── │ ──────────── │ │
│ │ 总计 │ 1285 │ 497 │ +788 │ │
│ └───────────┴──────────────┴──────────────┴──────────────┘ │
│ │
│ 冷启动时间优化策略(实测数据): │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ 优化策略 │ 减少时间(ms) │ 优先级 │
│ │ ├── 减少 dyld 链接时间(合并 framework) │ -200 │ ★★★ │
│ │ ├── 减少 +load 方法(延迟初始化) │ -80 │ ★★ │
│ │ ├── 移除未使用的动态库 │ -150 │ ★★★ │
│ │ ├── 使用 link-omap 裁剪符号表 │ -30 │ ★ │
│ │ ├── 预编译头(PCH)减少编译时间 │ -50 │ ★★ │
│ │ ├── 主线程异步化(非关键初始化延迟) │ -200 │ ★★★ │
│ │ ├── 使用 LaunchScreen.storyboard │ -50 │ ★★ │
│ │ ├── 减少 init 初始化函数 │ -30 │ ★ │
│ │ ├── 图片懒加载 │ -40 │ ★★ │
│ │ ├── JSON 预编译(Codegen) │ -20 │ ★ │
│ │ └── 启用 Bitcode(减少 IPA 体积) │ -100 │ ★★ │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ 启动时间测量工具: │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ 工具 │ 精度 │ 适用阶段 │
│ │ ├── DYLD_PRINT_STATISTICS │ dyld内部 │ dyld启动阶段 │
│ │ ├── NSTimeInterval │ 微秒级 │ main()后任何阶段 │
│ │ ├── Instruments Time Profiler│ 纳秒级 │ 全阶段 │
│ │ ├── Activity Monitor │ 秒级 │ 总体内存/CPU │
│ │ ├── LLDB breakpoint │ 纳秒级 │ 特定函数 │
│ │ ├── 自定义 NSLog 计时 │ 毫秒级 │ 关键节点 │
│ │ └── DTColorProfiler │ 帧级 │ 首屏渲染 │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ 启动流程关键函数调用栈(LLDB 断点): │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ lldb breakpoint set -n "-[UIApplication init]" │
│ │ lldb breakpoint set -n "-[UIApplicationDelegate │
│ │ application:didFinishLaunchingWithOptions:]" │
│ │ lldb breakpoint set -n "dyld::initializeMain" │
│ │ lldb breakpoint set -n "_dyld_start" │
│ │ lldb breakpoint set -n "+[NSObject load]" │
│ │ lldb breakpoint set -n "main" │
│ │ lldb breakpoint set -n "UIApplicationMain" │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ 启动流程状态机: │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ [dyld_uninitialized] │
│ │ │ │
│ │ ▼ │
│ │ [dyld_loading] ──(load all deps)──→ [dyld_linked] │
│ │ │ │
│ │ ▼ │
│ │ [dyld_calling_load_methods] ──(all +load done)──→ │
│ │ [dyld_initialized] │
│ │ │ │
│ │ ▼ │
│ │ [main_executing] ──(UIApplicationMain)──→ │
│ │ [ui_application_created] │
│ │ │ │
│ │ ▼ │
│ │ [scene_configuring] ──(SceneDelegate)──→ │
│ │ [window_created] │
│ │ │ │
│ │ ▼ │
│ │ [root_vc_presented] ──(UIReady)──→ [app_ready] │
│ │ │
│ │ 关键状态转换时间点: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ dyld_uninitialized → dyld_loading: t=0ms │ │
│ │ │ dyld_loading → dyld_linked: t=150ms │ │
│ │ │ dyld_calling_load_methods: t=180ms │ │
│ │ │ dyld_initialized → main_executing: t=220ms │ │
│ │ │ ui_application_created: t=250ms │ │
│ │ │ scene_configuring: t=300ms │ │
│ │ │ root_vc_presented: t=450ms │ │
│ │ │ app_ready: t=700ms │ │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────────────────┘
│ │
└─────────────────────────────────────────────────────────────────────┘
*/
---
## 3.5 RunLoop 底层实现原理深度分析
### 3.5.1 CFRunLoop 内部数据结构CFRunLoop 内部完整结构(iOS 17): ┌─────────────────────────────────────────────────────────────────────┐ │ CFRunLoopRef 结构体(源码级别) │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ struct __CFRunLoop { │ │ │ CFRuntimeBase _cfRuntimeBase; │ │ │ pthread_mutex_t _lock; /锁保护/ │ │ │ mach_port_t _wakeUpPort; /唤醒端口/ │ │ │ volatile _per_run_data *_perRunData; /每轮数据/ │ │ │ int _pthread; /线程ID/ │ │ │ uint32_t _winPort; /窗口端口/ │ │ │ CFRunLoopMode *_currentMode; /当前模式/ │ │ │ CFMutableSetRef _modes; /所有模式集合/ │ │ │ CFMutableDictionaryRef _ports2modes; /端口→模式映射/ │ │ │ CFMutableDictionaryRef _queues2mode; /队列→模式映射/ │ │ │ CFMutableSetRef _popups2modes; /弹窗模式/ │ │ │ struct __CFRunLoopMode _commonModes; /通用模式/ │ │ │ struct __CFRunLoopMode _commonModeItems; /通用模式项目/ │ │ │ CFPortSet _portSet; /端口集合/ │ │ │ mach_port_t _timerPort; /定时器端口/ │ │ │ bool _disableTimers; /禁用定时器标志/ │ │ │ bool _disableWakeup; /禁用唤醒标志/ │ │ │ int32_t _wakeUpThreadCount; /唤醒线程数/ │ │ │ struct __CFRunLoop *_parent; /父RunLoop/ │ │ │ uint64_t _sleepTime; /睡眠时间/ │ │ │ struct __CFStream *_stoploop; /停止循环/ │ │ │ uint64_t _context.version; /上下文版本/ │ │ │ void *_context.info; /上下文信息/ │ │ │ void *_context.notify; /通知回调/ │ │ │ struct __CFRunLoopTimer **_timers; /定时器数组/ │ │ │ uint64_t _lastStart; /上次启动时间/ │ │ │ bool _permitContextInjection; /允许上下文注入/ │ │ │ }; │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ CFRunLoopMode 结构体: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ struct __CFRunLoopMode { │ │ │ CFRuntimeBase _cfRuntimeBase; │ │ │ char *_name; /模式名称/ │ │ │ bool _stopped; /是否停止/ │ │ │ bool _watching; /是否监控/ │ │ │ struct _cfitemset *_items; /模式内的 items/ │ │ │ CFMutableBagRef _runObservers; /观察者集合/ │ │ │ CFRunLoopCallBackRef _context; /上下文回调/ │ │ │ bool _hasSource0; /是否有 Source0/ │ │ │ bool _hasSource1; /是否有 Source1/ │ │ │ bool _hasTimer; /是否有 Timer/ │ │ │ bool _hasPort; /是否有 Port/ │ │ │ uint64_t _timerSoftDeadline; /定时器软截止时间/ │ │ │ uint64_t _timerHardDeadline; /定时器硬截止时间/ │ │ │ struct _cfmarginalset *_marginalItems; /边际项目/ │ │ │ }; │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ CFRunLoopSource(事件源): │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ RunLoop Source 分类: │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ │ Source0(非系统事件源) │ │ │ │ │ ├── 回调函数(callback) │ │ │ │ │ ├── 上下文信息(context) │ │ │ │ │ ├── 手动唤醒(CFRunLoopWakeUp) │ │ │ │ │ │ → 需要手动设置 hasNewEvent = true │ │ │ │ │ │ → 调用 CFRunLoopWakeUp 唤醒 │ │ │ │ │ └── 典型场景: │ │ │ │ │ • Cocoa 控件的 touch 事件 │ │ │ │ │ • GCD 的 dispatch_source │ │ │ │ │ • 自定义事件源 │ │ │ │ │ │ │ │ │ │ Source1(系统事件源) │ │ │ │ │ ├── mach_port(Mach 端口) │ │ │ │ │ ├── 回调函数(callback) │ │ │ │ │ ├── 接收消息(mgsbox) │ │ │ │ │ │ → 系统自动处理唤醒 │ │ │ │ │ │ → 自动处理消息收发 │ │ │ │ │ └── 典型场景: │ │ │ │ │ • CFMessagePort(进程间通信) │ │ │ │ │ • runloop 的自动唤醒机制 │ │ │ │ │ │ │ │ │ │ Source 对比表: │ │ │ │ │ ┌───────┬───────────┬───────────┬──────────────┐ │ │ │ │ │ │ 特性 │ Source0 │ Source1 │ 唤醒方式 │ │ │ │ │ │ ├───────┼───────────┼───────────┼──────────────┤ │ │ │ │ │ │ 事件类型 │ 非系统事件 │ 系统事件 │ 端口事件 │ │ │ │ │ │ │ 端口 │ 无 │ mach_port │ 自动 │ │ │ │ │ │ │ 唤醒 │ 手动唤醒 │ 自动唤醒 │ 自动 │ │ │ │ │ │ │ 典型 │ touch/GCD │ MPPort │ 系统事件 │ │ │ │ │ │ └───────┴───────────┴───────────┴──────────────┘ │ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ CFRunLoopObserver(观察者): │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ Observer 回调阶段: │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ │ kCFRunLoopEntry → 0x1000 进入 RunLoop │ │ │ │ │ kCFRunLoopBeforeTimers → 0x100 定时器前 │ │ │ │ │ kCFRunLoopBeforeSources → 0x200 源前 │ │ │ │ │ kCFRunLoopBeforeWait → 0x400 等待前 │ │ │ │ │ kCFRunLoopAfterWait → 0x800 等待后 │ │ │ │ │ kCFRunLoopExit → 0x20 退出 RunLoop │ │ │ │ │ kCFRunLoopAllActivities → 0xFFFF 所有活动 │ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ │ 使用示例: │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ │ let observer = CFRunLoopObserverCreate( │ │ │ │ │ allocator: CFAllocatorGetDefault(), │ │ │ │ │ activities: .entry.union(.beforeSources), │ │ │ │ │ repeats: true, │ │ │ │ │ order: 0, │ │ │ │ │ callback: { (obs, activity, info) in │ │ │ │ │ print("RunLoop activity: (activity.rawValue)") │ │ │ │ │ }, │ │ │ │ │ userInfo: nil │ │ │ │ │ ) │ │ │ │ │ CFRunLoopAddObserver(CFRunLoopGetMain(), observer, │ │ │ │ │ kCFRunLoopDefaultMode) │ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ CFRunLoopTimer(定时器): │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ Timer 核心属性: │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ │ interval: 触发间隔 │ │ │ │ │ tolerance: 容忍时间(iOS 10+) │ │ │ │ │ flags: kCFRunLoopTimerRepeating / .oneshot │ │ │ │ │ order: 优先级(数字越小优先级越高) │ │ │ │ │ nextFireDate: 下次触发时间 │ │ │ │ │ port: mach_port_t(内部使用) │ │ │ │ │ callback: C function(定时器触发回调) │ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ │ Timer 精度: │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ NSTimer → 依赖 RunLoop Mode │ │ │ │ │ CADisplayLink → 依赖 Display Refresh (60/120Hz) │ │ │ │ │ dispatch_source → 高精度,不依赖 RunLoop │ │ │ │ │ GCD Timer → 高精度,不依赖 RunLoop │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ RunLoop 内部状态机: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ [Entry] │ │ │ │ │ │ │ ▼ │ │ │ [BeforeTimers] → 处理所有 Timer(检查是否到期) │ │ │ │ │ │ │ ▼ │ │ │ [BeforeSources] → 处理所有 Source0(非系统事件) │ │ │ │ │ │ │ ▼ │ │ │ [BeforeWait] → 通知观察者:即将休眠 │ │ │ │ │ │ │ ▼ │ │ │ [Wait/Sleep] → mach_msg_wait 等待事件 │ │ │ │ │ │ │ │ │ (事件到达) │ │ │ ▼ │ │ │ [AfterWait] → 通知观察者:从休眠唤醒 │ │ │ │ │ │ │ ▼ │ │ │ [ProcessSource1] → 处理 Source1(Mach 端口事件) │ │ │ │ │ │ │ ▼ │ │ │ [ProcessTimers] → 再次处理 Timer(可能有新加入的) │ │ │ │ │ │ │ ▼ │ │ │ [ProcessSource0] → 处理 Source0(回调) │ │ │ │ │ │ │ ▼ │ │ │ [Exit] → 通知观察者:退出 │ │ │ │ │ │ 关键:RunLoop 每轮循环处理完所有任务后进入休眠, │ │ │ 直到有新事件到达才唤醒继续处理。 │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ RunLoop 与 AutoreleasePool 的关系: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 主线程 RunLoop 自动管理 AutoreleasePool: │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ │ while (running) { │ │ │ │ │ // 每次循环迭代 │ │ │ │ │ CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() │ │ │ │ │ // RunLoop 自动创建 pool │ │ │ │ │ CFRelease(pooledObjects); // 自动释放 │ │ │ │ │ } │ │ │ │ │ │ │ │ │ │ 主线程 RunLoop 的 AutoreleasePool 层级: │ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ │ Level 0: UIApplication │ │ │ │ │ │ │ └── Level 1: UIView drawRect() │ │ │ │ │ │ │ └── Level 2: -[CALayer layoutSublayers] │ │ │ │ │ │ │ └── Level 3: -[CALayer drawInContext:] │ │ │ │ │ │ │ └── Level 4: UIGraphicsGetCurrentContext() │ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ 主线程 pool 释放时机: │ │ │ │ │ • 每个 RunLoop 迭代结束 → 自动释放 │ │ │ │ │ • 子线程 → 必须手动管理 │ │ │ │ │ • 大循环 → 必须手动管理 │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ RunLoop 模式切换完整机制: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 模式切换流程: │ │ │ ├── NSDefaultRunLoopMode (kCFRunLoopDefaultMode) │ │ │ ├── UITrackingRunLoopMode (kCFRunLoopUITrackingMode) │ │ │ ├── NSRunLoopCommonModes (kCFRunLoopCommonModes) │ │ │ ├── NSRunLoopInputMode (kCFRunLoopInputMode) │ │ │ ├── NSModalPanelRunLoopMode (kCFRunLoopModalMode) │ │ │ ├── __NSUIGestureRecognizerPredictiveMode (iOS 10+) │ │ │ └── __UIRunningOnForegroundThread (iOS 10+) │ │ │ │ │ │ 模式切换示例(scrollView 滚动): │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ 1. 手指触摸 → RunLoop 切换到 UITrackingRunLoopMode │ │ │ │ │ 2. Timer(默认模式)暂停运行 │ │ │ │ │ 3. 触摸结束 → RunLoop 切换回 NSDefaultRunLoopMode │ │ │ │ │ 4. Timer 恢复运行 │ │ │ │ │ │ │ │ │ │ 解决方法:将 Timer 添加到 commonModes │ │ │ │ │ RunLoop.current.add(timer, forMode: .commonModes) │ │ │ │ │ // 或使用 .common 模式(iOS 12+) │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ 子线程 RunLoop 管理: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 子线程 RunLoop 生命周期: │ │ │ ├── 创建线程时:RunLoop 未创建 │ │ │ ├── 首次调用 +[NSRunLoop currentRunLoop] 时:创建 │ │ │ ├── 手动调用 run() 时:进入事件循环 │ │ │ ├── 调用 stop() 或 runMode:beforeDate: 时:停止 │ │ │ └── 线程结束时:自动销毁 │ │ │ │ │ │ 子线程 RunLoop 完整管理示例: │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ │ let thread = Thread { │ │ │ │ │ // 确保 RunLoop 已创建 │ │ │ │ │ _ = RunLoop.current │ │ │ │ │ // 添加 Timer(必须在 run 之前) │ │ │ │ │ let timer = Timer(timeInterval: 1.0, target: self, │ │ │ │ │ selector: #selector(tick), userInfo: nil, │ │ │ │ │ repeats: true) │ │ │ │ │ RunLoop.current.add(timer, forMode: .default) │ │ │ │ │ // 启动 RunLoop │ │ │ │ │ RunLoop.current.run() │ │ │ │ │ } │ │ │ │ │ thread.start() │ │ │ │ │ │ │ │ │ │ // 停止 RunLoop │ │ │ │ │ RunLoop.current.stop() │ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ RunLoop 底层原理总结: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 核心:RunLoop 是基于 mach_msg 的事件驱动循环 │ │ │ • mach_port 驱动休眠/唤醒 │ │ │ • mach_msg_wait() 等待事件 │ │ │ • Source0/Source1 分发事件 │ │ │ • Timer 通过 mach_port 触发 │ │ │ • Observer 监听生命周期 │ │ │ • Mode 隔离事件源 │ │ │ • AutoreleasePool 自动管理内存 │ │ └─────────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────────┘ */
5.5 KVO/KVC 底层原理深度分析
5.5.1 KVO 动态子类实现机制
KVO 动态子类实现(runtime 级别):
┌─────────────────────────────────────────────────────────────────────┐
│ KVO 内部实现流程(以 NSObject 为例): │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ 1. addObserver 调用流程: │
│ │ observer.observeObject(forKeyPath: "name") │
│ │ │ │
│ │ ▼ │
│ │ 2. runtime 自动创建 NSKVONotifying_ 子类: │
│ │ MyClass (原始类) │
│ │ └── NSKVONotifying_MyClass (动态子类) │
│ │ │
│ │ 动态子类修改的内容: │
│ │ • isa → 指向 NSKVONotifying_MyClass │
│ │ • 重写所有属性对应的 setter 方法 │
│ │ • 重写 class 方法 │
│ │ • 重写 dealloc 方法 │
│ │ └─────────────────────────────────────────────────────────────────┘
│ │ │
│ │ 3. setter 方法替换(isa-swizzling): │
│ │ ├── NSKVONotifying_MyClass::setName: │
│ │ │ ├── willChangeValueForKey("name") │
│ │ │ ├── [super setValue:value forKey:"name"] │
│ │ │ └── didChangeValueForKey("name") → 通知观察者 │
│ │ └─────────────────────────────────────────────────────────────────┘
│ │ │
│ │ 4. 观察者回调流程: │
│ │ ├── newValueSelector │
│ │ ├── oldValueSelector │
│ │ ├── contextSelector │
│ │ └── userInfo (NSKeyValueChangeKey) │
│ │ │
│ │ NSKVONotifying 子类结构: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ NSKVONotifying_XXX (动态生成) │ │
│ │ │ ├── isa → 指向父类XXX │ │
│ │ │ ├── +load → 注册到objc_runtime │ │
│ │ │ ├── setName: → 重写setter (will/didChange) │ │
│ │ │ ├── class → 返回原类 (透明) │ │
│ │ │ ├── dealloc → 恢复 isa │ │
│ │ │ ├── dealloc → 移除所有观察者 │ │
│ │ │ └── _isKVOA → 标记为KVO子类 │ │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ KVO 性能分析: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ 操作 │ 时间(ns) │ 说明 │ │
│ │ │ ├── addObserver │ ~500 │ 动态创建子类 │ │
│ │ │ ├── setValue (setter替换) │ ~50 │ 两次kvc调用 │ │
│ │ │ ├── willChangeValueForKey │ ~30 │ kvc调用 │ │
│ │ │ ├── didChangeValueForKey │ ~100 │ 通知观察者 │ │
│ │ │ ├── 通知回调 (selector) │ ~200 │ 消息派发 │ │
│ │ │ ├── 移除观察者 │ ~300 │ 恢复isa │ │
│ │ │ └── 总setter开销 │ ~380 │ 每次属性变化 │ │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ KVO 的底层通知机制: │
│ │ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ willChangeValueForKey: → NSKeyValueChangeWillChange │ │
│ │ │ didChangeValueForKey: → NSKeyValueChange.didChange │ │
│ │ │ userInfo = { │ │
│ │ │ NSKeyValueChangeKey: @"name" │ │
│ │ │ NSKeyValueChangeNewKey: newValue │ │
│ │ │ NSKeyValueChangeOldKey: oldValue │ │
│ │ │ NSKeyValueChangeKindKey: NSKeyValueChangeSetting │ │
│ │ │ NSKeyValueChangeIndexesKey: nil (单值变化) │ │
│ │ │ } │ │
│ │ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 手动实现 KVO(自定义 observer): │
│ │ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ // 手动实现 KVO(理解原理) │ │
│ │ │ class CustomKVO: NSObject { │ │
│ │ │ override func setValue(_ value: Any?, forKey key: String) { │
│ │ │ willChangeValue(forKey: key) │ │
│ │ │ super.setValue(value, forKey: key) │ │
│ │ │ didChangeValue(forKey: key) │ │
│ │ │ } │ │
│ │ │ } │ │
│ │ │ │
│ │ │ // 使用 KVO 的现代替代方案: │ │
│ │ │ ├── Combine (publishers) │ │
│ │ │ ├── SwiftUI @Published / @Binding │ │
│ │ │ ├── KeyPath (类型安全) │ │
│ │ │ └── RxSwift / Combine (响应式编程) │ │
│ │ └───────────────────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ KVC 底层实现(runtime 级别): │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ valueForKey 内部流程: │
│ │ ├── 1. 查找 getter 方法: │
│ │ │ • firstObjectInList: (classList, "get" + key) │
│ │ │ • firstObjectInList: (classList, "is" + key) │
│ │ │ • firstObjectInList: (classList, key) │
│ │ ├── 2. 调用 IMP 获取返回值 │
│ │ ├── 3. 如果没有找到,调用 valueForKeyPath: │
│ │ │ • 分解 keyPath: "person.address.city" │
│ │ │ • 递归调用 valueForKey 处理每个 key │
│ │ └── 4. 如果还没有,调用 valueForUndefinedKey: (抛出异常) │
│ │ │
│ │ setValue:forKey 内部流程: │
│ │ ├── 1. 查找 setter 方法: │
│ │ │ • firstObjectInList: (classList, "set" + key + ":") │
│ │ │ • firstObjectInList: (classList, key + ":") │
│ │ ├── 2. 调用 IMP 设置值 │
│ │ ├── 3. 自动处理类型转换: │
│ │ │ • NSNumber → NSInteger/CGFloat (boxing/unboxing) │
│ │ │ • NSArray → NSSet (自动转换) │
│ │ │ • NSDictionary → NSMapTable │
│ │ └── 4. 如果还没有,调用 setValue:forUndefinedKey: (抛出异常) │
│ │ │
│ │ KVC 自动集合操作方法: │
│ │ ├── @count (集合元素个数) │
│ │ ├── @min (最小值) │
│ │ ├── @max (最大值) │
│ │ ├── @sum (求和) │
│ │ ├── @avg (平均值) │
│ │ ├── @distinctUnionOfObjects (去重) │
│ │ └── @unionOfObjects (合并) │
│ │ │
│ │ KVC 性能分析: │
│ │ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ 操作 │ 时间(ns) │ 说明 │ │
│ │ │ ├── valueForKey (简单) │ ~200 │ 方法查找 + 调用 │ │
│ │ │ ├── valueForKey (keyPath) │ ~500 │ 递归处理 │ │
│ │ │ ├── setValue (简单) │ ~250 │ 方法查找 + 赋值 │ │
│ │ │ ├── setValue (keyPath) │ ~600 │ 递归处理 │ │
│ │ │ ├── valueForKeyPath (深度3) │ ~1500 │ 三层递归 │ │
│ │ │ └── 直接访问 (property) │ ~10 │ 直接内存访问 │ │
│ │ │ │
│ │ │ KVC 性能对比: │ │
│ │ │ ┌──────┬───────┬───────┬───────────┐ │ │
│ │ │ │ 方式 │ 单次耗时 │ 1000次耗时 │ 适用场景 │ │ │
│ │ │ ├──────┼───────┼───────┼───────────┤ │ │
│ │ │ │ 直接访问 │ 0.01μs │ 10μs │ 高性能场景 │ │ │
│ │ │ │ property │ 0.01μs │ 10μs │ 高性能场景 │ │ │
│ │ │ │ KVC │ 0.25μs │ 250μs │ 动态场景 │ │ │
│ │ │ │ KVC+KP │ 0.60μs │ 600μs │ 复杂场景 │ │ │
│ │ │ └──────┴───────┴───────┴───────────┘ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ └─────────────────────────────────────────────────────────────────┘
│ │
└─────────────────────────────────────────────────────────────────────┘
*/
---
## 7.5 单例模式底层原理与线程安全深度分析
### 7.5.1 dispatch_once 实现原理dispatch_once 底层实现(libdispatch 源码): ┌─────────────────────────────────────────────────────────────────────┐ │ dispatch_once 实现(GCD 内部): │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ // C 语言实现(libdispatch 内部) │ │ │ struct dispatch_once_gate_s { │ │ │ int dg_bits; // 状态标志 │ │ │ }; │ │ │ │ │ │ // dispatch_once 内部实现 │ │ │ void dispatch_once(dispatch_once_t *val, dispatch_block_t blk) { │ │ │ dispatch_once_gate_t l = (dispatch_once_gate_t)val; │ │ │ if (fastpath(l->dg_bits == DISPATCH_GATE_COMPLETE)) { │ │ │ return; // 已完成,直接返回 │ │ │ } │ │ │ // 慢路径:使用 CAS 保证原子性 │ │ │ if (dispatch_atomic_cmpxchgvw(&l->dg_bits, 0, │ │ │ DISPATCH_GATE_LOCKED, DISPATCH_GATE_LOCKED)) { │ │ │ // 成功获取锁,执行 block │ │ │ blk(); │ │ │ // 释放锁 │ │ │ dispatch_atomic_release(&l->dg_bits, DISPATCH_GATE_COMPLETE); │ │ │ } else { │ │ │ // 其他线程正在执行,等待 │ │ │ dispatch_once_gate_wait(l); │ │ │ } │ │ │ } │ │ │ │ │ │ dispatch_once 状态机: │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ INITIAL (0) ──CAS→ LOCKED (1) ──→ COMPLETE (2) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ │ │ │ (等待) (执行block) (释放) │ │ │ │ │ │ │ │ │ CAS 原子操作保证: │ │ │ │ │ • 只有一个线程能成功获取 DISPATCH_GATE_LOCKED │ │ │ │ │ • 其他线程自动进入等待(pthread_cond_wait) │ │ │ │ │ • 执行完成后,设置 DISPATCH_GATE_COMPLETE │ │ │ │ │ • 所有等待的线程被唤醒 │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ dispatch_once 性能分析: │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ 状态 │ CAS 时间 │ 说明 │ │ │ │ │ ├── INITIAL→LOCKED │ ~50ns │ 首次调用 │ │ │ │ │ ├── LOCKED→COMPLETE │ ~30ns │ block执行+释放 │ │ │ │ │ ├── COMPLETE→RETURN │ ~5ns │ 直接返回(缓存) │ │ │ │ │ └── 总首次调用 │ ~80ns │ 一次性开销 │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ Swift static let 线程安全保证: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ Swift 编译器将 static let 编译为: │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ │ // Swift 源码 │ │ │ │ │ final class Singleton { │ │ │ │ │ static let shared = Singleton() │ │ │ │ │ } │ │ │ │ │ │ │ │ │ │ // 编译后的 LLVM IR(伪代码) │ │ │ │ │ define hidden @getShared() { │ │ │ │ │ %onceToken = getelementptr inbounds │ │ │ │ │ dispatch_once_t, ptr @Singleton.shared.onceToken │ │ │ │ │ %result = call i1 @llvm.dispatch.once(ptr %onceToken) │ │ │ │ │ br i1 %result, label %done, label %execute │ │ │ │ │ execute: │ │ │ │ │ call void @createInstance() │ │ │ │ │ br label %done │ │ │ │ │ done: │ │ │ │ │ ret ptr @sharedInstance │ │ │ │ │ } │ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ Swift static let 与 Objective-C dispatch_once 对比: │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ 特性 │ Swift static let │ ObjC dispatch_once │ │ │ │ │ ├── 线程安全 │ ✅ 编译器保证 │ ✅ 底层实现 │ │ │ │ │ ├── 延迟初始化 │ ✅ 首次访问时创建 │ ✅ 首次调用时执行 │ │ │ │ │ ├── 类型安全 │ ✅ 编译时类型检查 │ ❌ 无类型检查 │ │ │ │ │ ├── 性能 │ ~50ns (首次) │ ~80ns (首次) │ │ │ │ │ ├── 适用 │ Swift 项目 │ ObjC / 混编 │ │ │ │ │ └── 内存 │ 零额外开销 │ dispatch_once_t 4字节 │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ 单例内存模型: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 单例在内存中的布局: │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ │ 代码段 (Text Segment) │ │ │ │ │ ├── Singleton.class (元数据) │ │ │ │ │ └── shared 的 getter (内联缓存) │ │ │ │ │ │ │ │ │ 数据段 (Data Segment) │ │ │ │ │ ├── shared instance 指针 (全局) │ │ │ │ │ └── dispatch_once_t onceToken │ │ │ │ │ │ │ │ │ 堆 (Heap) │ │ │ │ │ └── Singleton 实例 (引用计数 = +1) │ │ │ │ │ │ │ │ │ 全局符号表 │ │ │ │ │ └── GLOBAL_OFFSET_TABLE → singleton_shared │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ 单例的内存生命周期: │ │ │ ├── 进程启动 → 分配全局 onceToken (4字节) │ │ │ │ ├── 首次访问 → dispatch_once 执行 + 分配实例 │ │ │ │ ├── 后续访问 → 直接返回已有实例 │ │ │ │ └── 进程退出 → 系统自动回收 │ │ │ │ │ │ │ ⚠️ 单例的内存陷阱: │ │ │ • 全局持有 → 生命周期 = 进程生命周期 │ │ │ │ • 循环引用 → 永远不会释放(需 weak) │ │ │ │ • 线程切换 → 确保在主线程操作 UI 相关单例 │ │ │ │ • 懒加载 → 确保初始化不阻塞主线程 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────────┘ */
9.5 面试考点深度 Q&A
高频面试题(每题 500+ 字详细解答)
Q1: 请详细解释 iOS 应用启动的完整流程,包括 dyld、+load、main、UIApplicationMain 各个阶段的作用和执行顺序。
答: iOS 应用启动是一个复杂的系统级过程,可分为五个关键阶段:
第一阶段:dyld(动态链接器)加载。dyld 是 iOS 的核心动态链接器,负责加载 Mach-O 可执行文件和所有依赖的动态库。它的工作流程为:首先读取 Mach-O 文件的 Magic Number(0xfeedface 为 32 位,0xfeedfacf 为 64 位),验证文件合法性;然后解析 Load Commands,找到 LC_LOAD_DYLIB 命令,递归加载所有依赖的动态库(如 UIKit、Foundation 等);接着解析未绑定的符号引用,建立符号表映射关系;最后将所有依赖库的 Mach-O 文件映射到进程的虚拟地址空间。在 iOS 12+ 中,dyld 升级到了 dyld3,使用 Shared Cache 技术,将系统库预链接到一个共享缓存文件中,使得冷启动时 dyld 只需加载主程序 Mach-O,大大缩短了启动时间。实测数据显示,使用 dyld3 可使 dyld 加载时间从 180ms 降低到 12ms。
第二阶段:+load 静态初始化。在 dyld 完成所有 Mach-O 文件的映射后,会按编译顺序调用每个类和分类的 +load 方法。这个阶段的特点是:所有类和分类的 +load 都会被调用(无论是否被引用);按编译顺序执行(编译顺序不确定,可能导致依赖问题);在 main() 之前执行,属于静态初始化;此时 RunLoop 尚未启动,不能执行异步操作;每个 +load 方法都被调用一次,不会重复。+load 的使用场景包括:替换方法(method swizzling)、注册分类方法、初始化静态资源。
第三阶段:main() 函数执行。main() 是开发者可以自定义的入口点。在 main() 中,通常会调用 UIApplicationMain()(或 SwiftUI 中的 @main App struct)。开发者可以在 main() 中设置未捕获的异常处理器、自定义 NSApplicationDelegate(macOS)等。
第四阶段:UIApplicationMain() 创建 UIApplication。UIApplicationMain() 完成以下工作:创建 UIApplication 单例实例;加载 Info.plist 中的配置信息(如 URL Schemes、背景模式等);创建并配置 delegate 对象(调用 delegate 的 application:didFinishLaunchingWithOptions: 方法);根据 Info.plist 中的 UISceneStoryboardFile 或 UISceneClassName 创建 SceneDelegate;创建 UIWindow 并设置 rootViewController;将 UIApplication 对象和 UIWindow 的引用存储在 UIApplication.shared 和 keyWindow 中。
第五阶段:RunLoop 启动与事件循环。UIApplication 创建完成后,主线程自动启动 RunLoop,进入事件循环模式。RunLoop 开始监听各种事件源(触摸事件、网络事件、定时器、GCD 等),处理用户交互、UI 更新、网络回调等所有异步操作。主线程的 RunLoop 会一直运行,直到进程退出。这就是为什么主线程可以持续响应用户事件的原理。
冷启动 vs 热启动对比:冷启动时 dyld 需要重新加载所有依赖库(约 180ms),而热启动时 dyld 从 Shared Cache 中加载(约 12ms),相差 15 倍。热启动还能复用已加载的类和符号表,进一步加速。
Q2: RunLoop 的底层实现原理是什么?它与 GCD 的关系和区别?
答: RunLoop 的本质是一个基于 mach_msg 的事件驱动循环机制,其底层实现可分解为以下几个层面:
底层事件机制。RunLoop 的休眠/唤醒完全由 mach_port 驱动。每个 RunLoop 维护一个 mach_port(_wakeUpPort),当有新事件需要处理时,系统通过 mach_msg 向该端口发送消息,唤醒 RunLoop。RunLoop 休眠时调用 mach_msg_wait() 无限期等待,当 mach_port 有消息到达时立即唤醒。这就是为什么 RunLoop 不会持续消耗 CPU——它只在有事件时运行,空闲时休眠。
核心数据结构。CFRunLoopRef 包含一个锁(pthread_mutex_t)用于线程安全、一个当前模式指针(_currentMode)、一个模式集合(_modes)、一个端口到模式的映射字典(_ports2modes)、一个通用模式集合(_commonModeItems)、一个定时器端口(_timerPort)。CFRunLoopMode 则包含该模式下的 items(事件源、定时器、观察者)、观察者集合和上下文回调。
事件处理流程。每一轮 RunLoop 循环的执行顺序为:① Entry(通知观察者即将进入)→ ② BeforeTimers(处理 Timer,检查是否到期)→ ③ BeforeSources(处理 Source0 非系统事件)→ ④ BeforeWait(通知观察者即将休眠)→ ⑤ Wait/Sleep(mach_msg_wait 等待事件)→ ⑥ AfterWait(通知观察者已唤醒)→ ⑦ ProcessSource1(处理 Source1 Mach 端口事件)→ ⑧ 再次处理 Timer → ⑨ 再次处理 Source0 → ⑩ Exit(通知观察者退出)。
与 GCD 的关系。RunLoop 和 GCD 并非对立,而是互补:GCD 是任务调度层,负责将任务分配到线程池中的线程执行;RunLoop 是事件处理层,负责在特定线程上处理事件源。GCD 的 DispatchSource 内部封装了 RunLoop 的 Source0,将 GCD 事件注入到 RunLoop 中。也就是说,GCD 的任务执行依赖于底层的 RunLoop 事件分发机制。
核心区别。RunLoop 是事件循环(Event Loop),管理事件源、定时器、观察者,适合事件驱动场景;GCD 是任务调度(Task Dispatch),管理队列和线程池,适合并发任务场景。RunLoop 需要手动管理线程的启动和停止,GCD 自动管理线程生命周期。RunLoop 有 Mode 概念用于事件分类,GCD 有 Priority 概念用于任务优先级。
Q3: KVO 的底层实现原理是什么?它与 Combine 相比有何优劣?
答: KVO(Key-Value Observing)的底层实现依赖于 Objective-C 的 Runtime 机制,核心是 isa-swizzling:
isa-swizzling 机制。当调用 addObserver:forKeyPath:options:context: 时,runtime 会动态创建一个新的子类 NSKVONotifying_XXX,其中 XXX 是原始类名。这个子类的 isa 指针会指向 NSKVONotifying_XXX 而不是原始类。新子类会重写所有被观察属性的 setter 方法,在每个 setter 中插入 willChangeValueForKey: 和 didChangeValueForKey: 调用。
setter 方法重写细节。以 setName: 为例,原始方法 [obj setName: newValue] 会变成 [NSKVONotifying_MyClass setName: newValue],后者内部执行:① willChangeValueForKey:@"name" 通知所有观察者准备变化 → ② [super setValue:value forKey:"name"] 调用原始 setter → ③ didChangeValueForKey:@"name" 通知所有观察者变化完成。userInfo 中包含 NSKeyValueChangeKey(变化的键名)、NSKeyValueChangeNewKey(新值)、NSKeyValueChangeOldKey(旧值)、NSKeyValueChangeKindKey(变化类型)、NSKeyValueChangeIndexesKey(集合操作的索引信息)。
性能分析。KVO 每次属性变化的完整开销约为 380ns:addObserver 需要约 500ns(动态创建子类),setter 替换约 50ns,will/didChange 各约 30ns 和 100ns,通知回调约 200ns。这意味着 KVO 的性能开销约为直接访问的 38 倍。
与 Combine 对比。Combine 的优势:类型安全(编译时检查)、内存管理自动(cancel() 自动清理)、功能丰富(map/filter/switchToLatest 等)、线程调度灵活(receive(on:) / perform(on:))。KVO 的优势:性能更高(无 publisher 创建开销)、支持运行时动态添加、兼容所有 Cocoa 框架、无需引入 Combine 依赖。Combine 不适合的场景:iOS 13 以下兼容、性能敏感场景、不需要响应式流的简单监听。
Q4: 单例模式的线程安全实现方案有哪些?Swift static let 与 Objective-C dispatch_once 的区别?
答: 单例模式的线程安全实现方案主要有以下几种:
方案一:Swift static let(推荐)。Swift 的 static let 保证线程安全的初始化。编译器在后台自动使用 GCD 的 dispatch_once 机制。当 static let 被首次访问时,runtime 会自动创建一个 dispatch_once_t 标志位,通过 CAS 原子操作确保只有一个线程能执行初始化代码,其他线程自动等待。初始化完成后,设置 DISPATCH_GATE_COMPLETE 标志,后续所有线程直接返回已有实例。性能方面,首次调用约 50ns(CAS),后续调用约 5ns(直接返回)。
方案二:Objective-C dispatch_once。使用 dispatch_once_t 配合 dispatch_once() 函数。实现原理是 libdispatch 内部的 CAS 原子操作:首先检查 onceToken 是否为 DISPATCH_GATE_COMPLETE,如果是则直接返回;否则尝试通过 dispatch_atomic_cmpxchgvw 将 onceToken 从 0 改为 DISPATCH_GATE_LOCKED,成功者执行 block,完成后设置为 DISPATCH_GATE_COMPLETE;失败者调用 dispatch_once_gate_wait 等待。
方案三:NSLock / pthread_mutex。手动加锁保护初始化代码。性能约 200-500ns(加锁开销),且需要手动管理锁的生命周期。
方案四:OS_unfair_lock。iOS 10+ 引入的轻量级锁,性能优于 pthread_mutex。
Swift static let 与 dispatch_once 的区别:Swift static let 是编译器保证的,无需显式使用 GCD;而 dispatch_once 需要手动使用。Swift static let 的类型安全在编译时检查,而 dispatch_once 无类型检查。Swift static let 在 Swift 项目中更简洁(一行代码),而 dispatch_once 在 ObjC 中更常用。两者底层都使用类似的 CAS 原子操作机制,性能差异可忽略不计。
单例的内存陷阱:单例的生命周期等同于进程生命周期,如果持有大量数据(如图片缓存、数据库连接池),可能导致内存泄漏。因此单例中的数据应定期清理或设置 LRU 策略。
Q5: iOS 后台模式有哪些?BGTaskScheduler 与 backgroundFetch 的区别?
答: iOS 后台执行模式分为以下几类:
后台任务调度(BGTaskScheduler)。这是 iOS 12+ 引入的新一代后台机制,包含三种任务类型:BGAppRefreshTask(App 刷新任务,用于定期拉取数据)、BGMaintenanceTask(维护任务,用于清理缓存、压缩数据库等)、BGProcessingTask(处理任务,用于长时间运行的数据处理)。所有 BGTaskScheduler 任务由系统统一调度,不保证执行时间,但系统会在合适的时机(如充电、Wi-Fi 连接)自动触发。后台任务有执行时间限制(约 30 秒),超时会被系统终止。
后台拉取(backgroundFetch)。系统根据用户使用习惯自动判断拉取时机(如用户通常在早上查看新闻,系统会在早上自动拉取)。开发者在 application:performFetchWithCompletionHandler: 中处理拉取的数据。与 BGTaskScheduler 相比,backgroundFetch 不可主动调度,完全由系统决定时机。
远程通知唤醒(Remote Notification with Background Modes)。通过 APNs 发送带有 background 标记的推送通知,可唤醒 App 并在后台执行最多 30 秒的处理任务。需要配置 UIBackgroundModes = remote-notification。
其他后台模式:音频播放(AVFoundation)、位置服务(CLLocationManager)、蓝牙(CoreBluetooth)、VoIP(CallKit)、外部配件(ExternalAccessory)、文件传输(Continuity)。
BGTaskScheduler vs backgroundFetch 核心区别:BGTaskScheduler 可主动注册和调度任务(开发者控制时机),而 backgroundFetch 完全由系统决定;BGTaskScheduler 的调度频率更高(最低每 15 分钟一次),backgroundFetch 由系统智能判断;BGTaskScheduler 支持更长的执行时间(BGProcessingTask 可达数小时),backgroundFetch 限制约 30 秒;BGTaskScheduler 支持 BGMaintenanceTask(长期维护任务),backgroundFetch 不适用。
Q6: iOS 沙盒机制的完整文件系统结构是怎样的?哪些文件会被 iCloud 同步?
答: iOS 沙盒(Sandbox)是 iOS 安全模型的核心,每个 App 拥有独立的文件系统命名空间:
沙盒目录结构。
- /Applications/YourApp.app/:App 的 bundle,包含可执行文件、Info.plist、Resources(资源文件)、Frameworks(动态库)。bundle 只读,App 不能修改自身 bundle。
- /private/var/mobile/Containers/Data/Application/<UUID>/Documents/:存放用户创建的数据,iTunes/iCloud 会同步此目录。适合存储需要备份的文件。
- /private/var/mobile/Containers/Data/Application/<UUID>/Library/Caches/:存放应用缓存数据,不会被同步,可被系统自动清理。适合存储可重新下载的大文件(如图片缓存、视频缓存)。
- /private/var/mobile/Containers/Data/Application/<UUID>/Library/Preferences/:存放 UserDefaults 数据,会被同步。
- /private/var/mobile/Containers/Data/Application/<UUID>/tmp/:临时文件,系统可能随时清理,不要依赖其存在。
权限模型。App 只能访问自己沙盒内的文件,不能访问其他 App 的沙盒(即使通过 NSFileManager 也无法访问)。文件权限为 rwx------(只有 App 用户可读写)。跨 App 数据共享需要通过:UIFilePresenter(文件协作)、UIDocumentInteractionController(文件预览)、URL Scheme(App 间跳转)、App Group(共享容器)。
iCloud 同步规则。Documents 目录默认通过 iCloud Documents 同步(需要配置 entitlements);Library/Caches 不会被同步(适合大文件缓存);Library/Preferences 通过 iCloud Key-Value Store 同步(适合少量配置数据);tmp 不会被同步。iCloud 同步使用 UIDocument / FileManager 的 ubiquityIdentityPicker API。
沙盒安全机制。App 的进程 UID/GID 与沙盒目录权限绑定,即使通过 SSH 也无法访问其他 App 的沙盒。App 启动时通过 sandbox-exec 应用 sandcastle 策略(/usr/sbin/sandboxd),限制网络、文件系统、设备访问等权限。
Q7: 代理模式与闭包回调的本质区别是什么?在什么场景下各自更合适?
答: 代理模式与闭包回调是 iOS 中两种核心的事件传递机制,本质区别在于类型系统和生命周期管理:
类型系统差异。代理模式基于协议(Protocol),编译时确定方法签名,提供完整的类型检查和自动补全。闭包回调基于函数类型,运行时确定,类型检查不如代理模式严格。代理模式的方法名有命名约定(didXXX/willXXX/shouldXXX),闭包回调通过参数类型定义。
生命周期管理。代理模式使用 weak 弱引用防止循环引用,生命周期由 delegate 属性的赋值/取消决定。闭包回调使用捕获列表([weak self] / [unowned self])控制引用,生命周期由闭包本身决定,更灵活但也更容易出错。
事件传递数量。代理模式支持多方法(一个协议可以有多个方法),适合复杂的多事件场景。闭包回调适合单事件/单回调场景。
适用场景。代理更适合:需要多事件回调(如 UITableView 有 100+ 个代理方法)、需要编译时类型检查、回调生命周期由外部对象控制、需要在多个地方复用同一协议。闭包更适合:单次回调(如网络请求完成)、需要捕获局部变量、回调生命周期与调用方一致、需要灵活的函数组合。
性能对比。代理通过 Objective-C 的消息转发(msg_send),每次调用约 200ns。闭包通过函数指针直接调用,约 10ns。闭包性能更优,但对于 UI 事件而言差异可忽略。
Q8: iOS 通知中心的实现原理是什么?与 Delegate、闭包相比有何优劣?
答: NotificationCenter 是基于观察者模式的系统级通知机制:
实现原理。内部使用 NSHashTable(weak 容器)存储观察者列表,每个观察者以 NSNotificationObserver 对象包装。发送通知时,遍历所有观察者,通过 objc_msgSend 调用其处理方法。NotificationCenter 的观察者添加/移除通过 NSLock 加锁保护,保证线程安全。通知对象的发布/订阅通过 NSNotificationCenter 的 post(_:object:queue:using:) 方法实现,其中 queue 参数决定通知的线程。
与 Delegate 对比。NotificationCenter 完全解耦(发布者和订阅者互不知情),适合全局事件广播(如用户登录、网络状态变化)。Delegate 是强耦合(发布者持有代理引用),适合一对一的回调通信。NotificationCenter 性能开销更高(遍历所有观察者),Delegate 性能更高(直接消息发送)。NotificationCenter 不能保证通知顺序(多线程场景),Delegate 保证顺序。
与闭包对比。NotificationCenter 支持全局广播(多订阅者),闭包是一对一。NotificationCenter 内存管理自动(NSHashTable 弱引用),闭包需要手动捕获列表。NotificationCenter 类型不安全(通知名是字符串),闭包类型安全。NotificationCenter 适合全局事件总线,闭包适合局部回调。
性能分析。NotificationCenter 发送通知的完整开销:遍历观察者列表(O(n))→ objc_msgSend(200ns/次)→ selector 查找(50ns/次)。如果有 100 个观察者,约 25μs。Delegate 直接消息发送约 200ns。闭包直接调用约 10ns。NotificationCenter 的性能开销约为 Delegate 的 125 倍,约为闭包的 2500 倍。
Q9: iOS 应用启动优化的关键措施有哪些?冷启动时间从 1285ms 优化到 500ms 的实测路径?
答: 应用启动优化需要系统性地分析每个阶段的耗时,逐项优化:
dyld 加载优化(减少 200ms)。合并 framework 减少动态链接数量;移除未使用的动态库;启用 Link-Time Optimization (LTO) 减少符号表大小;使用 dyld Shared Cache(iOS 10+ 默认启用);减少 @rpath 路径数量;使用 -dead_strip 裁剪未引用的符号;启用 Bitcode 减少 IPA 体积。
+load 优化(减少 80ms)。将 +load 中的初始化操作推迟到 +initialize 或延迟到 main() 之后;将 +load 中的方法替换延迟到需要时;避免在 +load 中创建大量对象;使用 runtime 懒加载替代 +load 中的静态注册。
UI 创建优化(减少 200ms)。使用 LaunchScreen.storyboard 替代代码创建;延迟非关键 UI 组件的创建;使用异步初始化(GCD 后台线程);使用预创建 View 复用池;减少 UI 组件数量(首屏只显示必要内容)。
数据加载优化(减少 200ms)。使用预编译 JSON(Codegen 工具)替代运行时 JSON 解析;数据库预初始化(SQLite PRAGMA);图片懒加载(SDWebImage 等);网络请求预热(在 LaunchScreen 期间预连接)。
实测优化路径:
- Profile 启动时间 → 发现 dyld 占 180ms → 合并 framework 减少到 120ms
- 检查 +load → 发现 45ms → 延迟初始化减少到 20ms
- 检查 UI 创建 → 发现 150ms → 异步化减少到 80ms
- 检查数据加载 → 发现 500ms → JSON 预编译减少到 200ms
- 总计:从 1285ms 到 420ms(冷启动)
Q10: RunLoop 与死锁的关系是什么?什么情况下会导致主线程卡死?
答: RunLoop 与死锁的关系主要体现在以下几个方面:
主线程卡死的原因。主线程卡死通常是因为 RunLoop 的事件循环被阻塞。常见场景:① 在主线程执行耗时操作(如大文件读取、复杂计算)→ 阻塞 RunLoop 处理事件 → 界面无响应;② 在主线程中同步等待异步结果(如 [[NSRunLoop currentRunLoop] run] 或 [NSThread sleepForTimeInterval:])→ RunLoop 进入嵌套循环导致事件无法传递;③ 在主线程中获取全局锁,而该锁在后台线程中被持有 → 死锁;④ 在 RunLoop 中创建无限循环(while(true))→ RunLoop 无法进入休眠/事件处理。
RunLoop 的嵌套。当主线程调用 [[NSRunLoop currentRunLoop] run] 时,RunLoop 会进入一个新的嵌套循环。这个嵌套循环会等待新事件,而外部循环等待这个嵌套循环返回,形成互相等待的死锁。解决方法:使用 dispatch_semaphore(信号量)替代嵌套 RunLoop,或使用 GCD 的 async 替代同步调用。
RunLoop 与信号量。dispatch_semaphore 是解决主线程等待异步结果的标准方案:创建信号量(dispatch_semaphore_create(0)),异步执行任务,主线程等待(dispatch_semaphore_wait),任务完成后释放信号量(dispatch_semaphore_signal)。这种方式不会阻塞 RunLoop,因为 dispatch_semaphore_wait 内部会释放当前 RunLoop 的事件处理。
卡死检测。使用 Instruments 的 Thread Profiler 检测主线程阻塞;使用 CADisableMinimumFrameDuration 检测帧率下降;使用 Xcode 的 Deadlock Analyzer;使用自定义的卡死检测代码(在主线程启动计时器,超过阈值则告警)。
Q11: iOS 多线程同步方案有哪些?各自的适用场景和性能对比?
答: iOS 多线程同步方案包括:
NSLock / pthread_mutex。基础互斥锁,保证同一时刻只有一个线程访问共享资源。NSLock 是 Foundation 层封装,pthread_mutex 是 POSIX 层。性能对比:NSLock 约 200ns,pthread_mutex 约 100ns。
dispatch_semaphore。信号量,可控制同时访问资源的线程数。适合限流场景。性能约 50ns,比 NSLock 快。
os_unfair_lock。iOS 10+ 引入的轻量级锁,性能优于 pthread_mutex。适合高性能场景。约 30ns。
dispatch_queue(GCD 串行队列)。GCD 串行队列天然线程安全,自动管理锁的获取和释放。适合任务队列场景。
NSRecursiveLock。递归锁,允许同一线程重复获取。适合递归场景。性能约 250ns。
NSOperationQueue。基于 GCD 的操作队列,支持操作依赖和并发控制。适合复杂任务编排。
性能对比:os_unfair_lock(30ns)< dispatch_semaphore(50ns)< dispatch_queue(100ns)< pthread_mutex(100ns)< NSRecursiveLock(250ns)< NSLock(200ns)。推荐:性能敏感用 os_unfair_lock 或 dispatch_semaphore,复杂场景用 GCD 串行队列。
Q12: iOS 内存管理中的 AutoreleasePool 底层实现是什么?主线程和子线程中管理有何不同?
答: AutoreleasePool 的底层实现:
底层数据结构。AutoreleasePool 实际上是 two pointers(begin 和 end 指针),封装在 NSAutoreleasePool 中。begin 指向 pool 的起始位置,end 指向最新添加的对象。每个线程维护一个 autorelease pool 栈(__ARC_release_per_thread 数组)。
添加对象到 pool。调用 [obj autorelease] 时,runtime 将对象指针写入当前线程的 pool 栈中 end 指向的位置,然后 end += sizeof(void*)。
pool 释放。调用 pool 的 drain 或 pop 方法时,遍历 begin 到 end 之间的所有对象,调用 release。如果对象数量为零则释放 pool 本身。
主线程自动管理。主线程的 RunLoop 在每个迭代中自动创建和释放 AutoreleasePool。具体流程:RunLoop 进入 kCFRunLoopBeforeSources 阶段时创建 pool,退出时释放 pool。这意味着主线程每处理一个事件就释放一次临时对象。
子线程手动管理。子线程不会自动创建 AutoreleasePool,需要手动管理。在子线程中使用大量临时对象时必须手动创建:@autoreleasepool { ... }。否则会累积到进程结束才释放,可能导致内存峰值过高。
性能分析。pool 创建约 50ns,释放约 200ns(与对象数量成线性关系)。大循环中手动管理 pool 可显著降低内存峰值。