Day14-RunLoop&多线程&内存管理
一、核心概念
1. RunLoop
- RunLoop 是线程的事件处理循环,是
NSRunLoop
和CFRunLoopRef
的封装。 - 默认只在主线程自动启动。
- 管理事件源(Timer、Input Source、Observer)。
- 保持线程活跃、延迟执行任务、监听输入等都依赖 RunLoop。
2. 多线程
- iOS 支持多线程方式有:
NSThread
GCD
(主流)NSOperationQueue
- 每条线程都有自己独立的 RunLoop,但默认只有主线程的 RunLoop 自动运行。
3. 内存管理(以 ARC 为基础)
- 使用
retain/release
管理对象生命周期。 strong/weak/unowned
控制引用关系。- 多线程场景中注意引用循环、野指针、线程同步问题。
二、三者之间的联系
RunLoop 与 多线程
- 子线程若需处理 Timer、事件监听,必须手动开启 RunLoop。
- 子线程默认 RunLoop 不启动,任务执行完就退出。
- 示例:
1
2
3
4
5
6
7
8- (void)startBackgroundThread {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"Start thread");
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
}
多线程 与 内存管理
- ARC 负责线程安全地管理引用计数。
- GCD 的 Block 默认强引用捕获外部变量,注意内存泄漏。
- 多线程中访问共享资源要小心并发读写带来的内存访问异常。
RunLoop 与 内存管理
- RunLoop 会持有它注册的事件源(Timer、Port 等)。
- 若 Timer target 对象被强引用,容易造成循环引用:
1
2// 错误示例
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
三、典型场景解析
场景 | 涉及点 | 注意事项 |
---|---|---|
子线程保持活跃 | RunLoop + 多线程 | 添加 Port / Timer 后手动启动 RunLoop |
定时器导致内存泄漏 | RunLoop + 内存管理 | 使用 weak self ,或中介对象解耦 |
异步任务中回调 UI | 多线程 + RunLoop | 回到主线程,主线程的 RunLoop 驱动 UI 刷新 |
GCD 死锁 | 多线程 | dispatch_sync 嵌套调用主队列容易死锁 |
RunLoop 的生命周期管理 | RunLoop + 内存管理 | 注意强引用阻止线程退出 |
四、面试问题举例
Q1: 子线程中 Timer 不执行,为什么?
A: 子线程默认 RunLoop 没有启动,Timer 依赖 RunLoop 执行。
Q2: NSTimer 为什么可能引起内存泄漏?如何避免?
A: 因为 NSTimer 强引用 target,target 又强引用 timer。使用
weak
或中间对象(如NSProxy
)解决循环引用。
Q3: GCD 的异步任务中为何 UI 更新失败?
A: UI 更新必须在主线程中执行,异步任务默认不在主线程,需要使用
dispatch_async(dispatch_get_main_queue(), ^{ })
。
Q4: RunLoop 如何保持线程常驻?
A: 使用
[[NSRunLoop currentRunLoop] run]
,或者添加事件源如 Port 或 Timer 保持 RunLoop 活跃。
五、总结图示
1 | 线程(Thread) |