1. UIViewController 生命周期详解
完整生命周期时序图
yaml
✅ 初始化阶段:
↓ initWithCoder: (Storyboard/XIB) 或 initWithNibName:bundle:
↓ loadView (创建视图层次)
↓ viewDidLoad (视图加载完成)
✅ 视图显示阶段:
↓ viewWillAppear: (视图即将显示)
↓ viewWillLayoutSubviews (即将布局子视图)
↓ viewDidLayoutSubviews (子视图布局完成)
↓ viewDidAppear: (视图完全显示)
✅ 视图消失阶段:
↓ viewWillDisappear: (视图即将消失)
↓ viewDidDisappear: (视图完全消失)
✅ 销毁阶段:
↓ dealloc (控制器销毁)
各方法使用场景和注意事项
objc
// 🌟 关键方法详解
class ViewControllerLifecycle {
// 1. loadView - 手动创建视图时重写
override func loadView() {
// ❌ 不要调用 super.loadView() 如果手动创建
self.view = CustomView()
}
// 2. viewDidLoad - 一次性初始化
override func viewDidLoad() {
super.viewDidLoad()
// ✅ 数据初始化、网络请求、UI配置
setupData()
configureUI()
}
// 3. viewWillAppear - 每次显示前调用
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ✅ 更新UI、开始动画、注册通知
updateContent()
startAnimation()
}
// 4. viewDidAppear - 显示完成后调用
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// ✅ 耗时操作、统计打点
trackAnalytics()
}
// 5. viewWillDisappear - 即将消失
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ✅ 保存数据、停止动画
saveData()
stopAnimation()
}
}
高频面试要点
viewDidLoadvsviewWillAppear: 前者只调用一次,后者每次页面显示都会调用- 布局时机 :
viewWillLayoutSubviews和viewDidLayoutSubviews在旋转屏幕、子视图变化时调用 - 内存管理 : 在
viewDidDisappear中释放不必要的资源,在dealloc中移除观察者
2. 为什么必须在主线程操作UI
根本原因分析
swift
class ThreadSafetyAnalysis {
// 🔒 UIKit 线程不安全的具体表现
let threadIssues = [
"1. 资源竞争": "多个线程同时修改UI状态",
"2. 预期外状态": "后台线程修改时主线程正在渲染",
"3. 内存访问冲突": "一个线程释放另一个线程正在使用的资源",
"4. 渲染时序错乱": "UI更新与Core Animation渲染不同步"
]
// ⚡ 技术层面原因
let technicalReasons = [
"• UIKit使用非线程安全的数据结构",
"• CALayer的隐式动画依赖主线程RunLoop",
"• 视图层次遍历需要原子性操作",
"• 图形上下文(Context)线程绑定"
]
}
实际崩溃场景示例
objc
// ❌ 错误示例 - 子线程更新UI
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.label.text = @"更新文本"; // 可能崩溃或显示异常
self.view.backgroundColor = [UIColor redColor]; // 可能无效
});
// ✅ 正确做法
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 后台处理数据
NSString *result = [self processData];
// 回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.label.text = result;
self.view.backgroundColor = [UIColor redColor];
});
});
扩展知识点
- RunLoop机制: 只有主线程的RunLoop默认开启并处理输入源
- 性能考量: 集中UI更新避免频繁线程切换
- 调试技巧 : 设置
UIViewAlertForNonMainThreadDrawing检测非主线程UI操作
3. 响应者链与事件传递机制
完整事件处理流程
objectivec
📱 用户触摸屏幕
↓ UIApplication 接收事件
↓ UIWindow 开始 hitTest 查找
↓ 递归调用 pointInside + hitTest
↓ 找到命中视图 (hit-test view)
↓ 沿着响应者链传递事件
↓ 找到处理该事件的响应者
核心方法实现原理
swift
class EventHandlingMechanism {
// 🎯 hitTest 方法内部实现
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1. 检查条件
guard !isHidden && alpha > 0.01 && isUserInteractionEnabled else {
return nil
}
// 2. 检查点是否在视图内
guard self.point(inside: point, with: event) else {
return nil
}
// 3. 逆序遍历子视图
for subview in subviews.reversed() {
let convertedPoint = convert(point, to: subview)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
// 4. 没有子视图处理,返回自身
return self
}
// 📍 pointInside 判断逻辑
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.contains(point)
}
}
响应者链传递规则
swift
// 响应者链典型路径
let responderChain = [
"初始视图 (hit-test view)",
"↑ 父视图",
"↑ ...",
"↑ 视图控制器 (如果存在)",
"↑ UIWindow",
"↑ UIApplication",
"↑ AppDelegate"
]
// 🌰 实际示例
let exampleChain = [
"UIButton",
"↑ UITableViewCell",
"↑ UITableView",
"↑ UIViewController",
"↑ UIWindow",
"↑ UIApplication"
]
高频面试要点
- hitTest调用顺序: 从后添加的子视图开始(显示在最上面的)
- 响应者链中断 : 实现
touchesBegan但不调用super会中断传递 - 应用场景: 自定义事件处理、扩大点击区域、事件拦截
4. weak 实现原理深度解析
Runtime 底层数据结构
objc
// weak 表结构示意
struct weak_table_t {
weak_entry_t *weak_entries; // hash 数组
size_t num_entries; // 条目数量
uintptr_t mask; // 容量掩码
uintptr_t max_hash_displacement; // 最大哈希偏移
};
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被弱引用对象
union {
struct {
weak_referrer_t *referrers; // 弱引用指针数组
uintptr_t out_of_line : 1; // 是否使用动态数组
};
weak_referrer_t inline_referrers[4]; // 内联小数组优化
};
};
weak 操作完整流程
swift
class WeakImplementation {
// 🔧 weak 赋值过程
func weakAssignmentProcess() {
let steps = [
"1. 获取对象的 weak_table",
"2. 创建或找到对应的 weak_entry_t",
"3. 将 weak 指针地址添加到 referrers 数组",
"4. 不增加对象的引用计数"
]
}
// 🗑️ 对象释放过程
func deallocationProcess() {
let steps = [
"1. 对象执行 dealloc",
"2. 在 weak_table 中查找所有 weak 引用",
"3. 遍历所有 weak 指针并将其置为 nil",
"4. 从 weak_table 中移除对应条目"
]
}
}
实际代码验证
objc
// 验证 weak 表工作机制
__weak id weakObj = nil;
{
NSObject *obj = [[NSObject alloc] init];
weakObj = obj;
NSLog(@"对象存活: %@", weakObj); // 有值
}
NSLog(@"对象释放: %@", weakObj); // nil
5. 自动释放池与内存管理
@autoreleasepool 正确使用
objc
// ❌ 问题代码分析
Person *p = [[[[Person alloc] init] autorelease] autorelease];
// 问题:对象被加入自动释放池两次,会导致过度释放崩溃
// ✅ 正确用法
@autoreleasepool {
for (int i = 0; i < 1000000; i++) {
NSString *str = [NSString stringWithFormat:@"large_string_%d", i];
// 每次迭代都会释放临时对象
}
}
Tagged Pointer 优化机制
objc
// 🌟 Tagged Pointer 验证
for (int i = 0; i < 1000000; i++) {
NSString *str1 = [NSString stringWithFormat:@"abc"]; // Tagged Pointer
NSString *str2 = [NSString stringWithFormat:@"abcdefghijklmn"]; // 堆分配
NSLog(@"str1: %p, str2: %p", str1, str2);
// str1: 0x...1 (Tagged Pointer,值编码在指针中)
// str2: 0x... (正常堆地址)
}
内存泄漏排查指南
swift
class MemoryLeakDetection {
// 🔍 常见内存泄漏场景
let leakScenarios = [
"1. Block循环引用": """
self.completion = { [weak self] in
self?.doSomething() // 必须使用weak
}
""",
"2. Delegate强引用": """
// 应该使用weak delegate
weak var delegate: SomeDelegate?
""",
"3. Timer未释放": """
// 必须手动invalidate
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in }
""",
"4. 大循环内存累积": """
// 使用autoreleasepool及时释放
for i in 0..<100000 {
autoreleasepool {
let data = Data(count: 1024 * 1024) // 1MB
process(data)
}
}
"""
]
// 🛠️ 检测工具推荐
let detectionTools = [
"Xcode Memory Debugger",
"Instruments Leaks",
"MLeaksFinder",
"FBRetainCycleDetector"
]
}
6. 高频面试进阶问题
性能优化相关
swcift
// ViewController 启动优化
class LaunchOptimization {
let strategies = [
"减少viewDidLoad中的耗时操作",
"异步加载非关键资源",
"使用懒加载延迟初始化",
"预加载关键数据但延迟渲染"
]
}
内存管理进阶
objc
// 自动释放池与RunLoop关系
- (void)runLoopAutoreleaseDemo {
// 主RunLoop每次迭代都会创建和释放autoreleasepool
// 因此短期对象会在一次事件循环后释放
}
NSOperation 与 GCD 深度对比及选型指南
一、核心架构差异
1. 底层实现
-
GCD (Grand Central Dispatch)
- 纯 C 语言的 API,更接近系统底层
- 苹果为多核并行运算提出的解决方案
- 基于 work queue 模型,由系统自动管理线程
-
NSOperation
- 基于 GCD 的 Objective-C 封装
- 面向对象的并发编程接口
- 提供更高级的抽象和更丰富的功能
二、功能特性对比
1. 队列管理能力
| 特性 | GCD | NSOperation |
|---|---|---|
| 队列类型 | 只支持 FIFO 队列 | 支持复杂的执行顺序控制 |
| 并发控制 | 无法直接设置最大并发数 | 可方便设置最大并发数量 |
| 队列状态 | 有限的控制能力 | 可暂停、恢复、取消队列 |
swift
// NSOperationQueue 高级队列控制示例
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 3 // 限制并发数
operationQueue.isSuspended = true // 暂停队列
operationQueue.cancelAllOperations() // 取消所有任务
2. 任务依赖管理
swift
// NSOperation 原生依赖支持
let downloadOperation = BlockOperation { print("下载数据") }
let parseOperation = BlockOperation { print("解析数据") }
let saveOperation = BlockOperation { print("保存数据") }
// 设置依赖关系
parseOperation.addDependency(downloadOperation)
saveOperation.addDependency(parseOperation)
// GCD 实现相同功能需要复杂的手动同步
let dispatchGroup = DispatchGroup()
// 需要大量样板代码来管理依赖关系
3. 任务状态监控
swift
// NSOperation 完整的 KVO 支持
class CustomOperation: Operation {
override func main() {
guard !isCancelled else { return }
// 执行任务逻辑
}
}
let operation = CustomOperation()
// 监听任务状态变化
operation.addObserver(self, forKeyPath: "isFinished", options: .new, context: nil)
operation.addObserver(self, forKeyPath: "isExecuting", options: .new, context: nil)
operation.addObserver(self, forKeyPath: "isCancelled", options: .new, context: nil)
// 实时检查任务状态
if operation.isExecuting {
print("任务正在执行")
}
// 取消任务
operation.cancel()
三、性能与适用场景
1. 性能对比
-
GCD 执行速度更快
- 直接使用 C API,系统调用开销更小
- 没有 Objective-C 消息传递的开销
- 内存管理更高效
-
NSOperation 稍有性能损失
- Objective-C 运行时开销
- 面向对象封装的额外成本
- KVO 通知的性能影响
2. 适用场景指南
✅ 推荐使用 GCD 的场景
swift
// 1. 简单的并发任务
DispatchQueue.global(qos: .background).async {
// 后台处理数据
let processedData = self.processLargeData()
DispatchQueue.main.async {
// 回到主线程更新UI
self.updateUI(with: processedData)
}
}
// 2. 延迟执行
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.showNotification()
}
// 3. 一次性执行
dispatchOnce {
// 初始化代码,只执行一次
}
// 4. 任务间无依赖的并行处理
let group = DispatchGroup()
for item in items {
group.enter()
processItem(item) {
group.leave()
}
}
group.notify(queue: .main) {
self.handleCompletion()
}
✅ 推荐使用 NSOperation 的场景
swift
// 1. 复杂的任务依赖关系
let operations = createOperationChain()
operationQueue.addOperations(operations, waitUntilFinished: false)
// 2. 需要精确控制的任务
let operation = CustomOperation()
operation.completionBlock = {
print("任务完成回调")
}
operationQueue.addOperation(operation)
// 3. 需要任务状态监控
monitorOperationProgress(operation)
// 4. 需要取消和暂停功能
if shouldCancelOperations {
operationQueue.cancelAllOperations()
}
// 5. 限制资源使用的场景
operationQueue.maxConcurrentOperationCount = 2 // 避免资源竞争
四、实际项目选型建议
选择矩阵
| 场景特征 | 推荐方案 | 理由 |
|---|---|---|
| 任务简单、无依赖 | GCD | 性能更好,代码简洁 |
| 复杂依赖关系 | NSOperation | 依赖管理更直观 |
| 需要任务状态监控 | NSOperation | 完整的 KVO 支持 |
| 高性能要求 | GCD | 底层 API,执行效率更高 |
| 需要取消/暂停 | NSOperation | 完善的任务生命周期管理 |
| 资源限制场景 | NSOperation | 可控制并发数量 |
混合使用策略
swift
class HybridApproach {
func optimalSolution() {
// 使用 NSOperation 管理复杂任务流
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 3
// 在 Operation 内部使用 GCD 进行高效并行
let dataProcessingOperation = BlockOperation {
let dispatchGroup = DispatchGroup()
// 使用 GCD 进行数据并行处理
for dataChunk in dataChunks {
dispatchGroup.enter()
DispatchQueue.global().async {
self.processDataChunk(dataChunk)
dispatchGroup.leave()
}
}
dispatchGroup.wait()
}
operationQueue.addOperation(dataProcessingOperation)
}
}
GCD 最佳实践
- 使用适当的 QoS 类别
- 避免线程爆炸,合理使用并发队列
- 注意主线程保护,及时切换回主线程更新UI
- 合理使用 DispatchGroup 管理任务组
NSOperation 最佳实践
- 合理设置最大并发数,避免资源竞争
- 及时取消不再需要的操作
- 使用 KVO 监控重要操作状态
- 利用依赖关系构建清晰的任务流水线
通用建议
- 根据具体场景选择合适的技术
- 在复杂业务逻辑中优先考虑可维护性
- 在性能关键路径上优先考虑执行效率
- 合理混合使用两种技术,发挥各自优势