一、RunLoop 的基本概念
1.1 什么是 RunLoop
- RunLoop(运行循环)是 iOS 和 macOS 系统中用于管理线程事件循环的基础设施。
- 它的本质是一个事件处理循环,不断地从事件源(输入源、定时源)中获取事件并分发处理。
- RunLoop 让线程在有任务时工作、无任务时休眠,从而节省系统资源。
1.2 线程与 RunLoop 的关系
- 每个线程都可以拥有自己的 RunLoop,主线程的 RunLoop 默认已经启动。
- 子线程的 RunLoop 默认没有启动,需要手动创建和启动。
- RunLoop 主要用于需要持续响应事件的线程(如主线程、需要定时器/端口/输入源的子线程)。
1.2.1 主线程 RunLoop 的启动
主线程的 RunLoop 在 UIApplicationMain() 启动时自动创建并启动:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
1.2.2 子线程 RunLoop 的获取和启动
- 获取当前线程的 RunLoop:
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
- 启动子线程的 RunLoop:
[[NSRunLoop currentRunLoop] run];
- 注意:Cocoa 的 NSRunLoop 不是线程安全的,不能跨线程操作。CoreFoundation 的 CFRunLoopRef 是线程安全的。
二、RunLoop 的常见应用场景
2.1 子线程中使用定时器
- 主线程的 RunLoop 默认启动,定时器能正常工作。
- 子线程中使用 NSTimer 时,必须手动启动 RunLoop,否则定时器不会触发。
- (void)test2 {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
- (void)newThread {
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
2.2 GCD 定时器
- GCD 的 dispatch_source_t 也依赖底层的 RunLoop 机制。
uint64_t interval = 0.01 * NSEC_PER_SEC;
dispatch_queue_t queue = dispatch_queue_create("my_queue", 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD定时器触发");
});
dispatch_resume(timer);
2.3 主线程定时器的 Mode 问题
- 主线程的 NSTimer 默认加到
NSDefaultRunLoopMode,如果遇到 UI 滚动等会被暂停。 - 可将 Timer 加到
NSRunLoopCommonModes,避免被 UI 事件影响。
self.timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
三、RunLoop 的原理与机制
3.1 RunLoop 的事件源
- 输入源(Input Source):如端口、Source0/Source1、用户输入等。
- 定时源(Timer Source):如 NSTimer、GCD Timer 等。
3.2 RunLoop 的运行模式(Mode)
-
NSDefaultRunLoopMode:默认模式,处理普通事件。 -
UITrackingRunLoopMode:用于 UI 追踪(如滚动)。 -
NSRunLoopCommonModes:包含一组常用模式,Timer 加到此模式可避免 UI 事件影响。
3.3 RunLoop 的优点
- 事件驱动,自动休眠和唤醒,节省系统资源。
- 高度抽象,简化事件处理流程。
- 支持定时器、输入源、端口等多种事件源。
- 自动管理 autorelease pool。
四、常见问题与补充
4.1 为什么主线程的定时器能用,子线程的不行?
- 主线程 RunLoop 默认启动,子线程需手动启动 RunLoop。
4.2 RunLoop 会不会造成性能问题?
- 不会,RunLoop 在没有事件时会自动休眠,只有事件到来时才会唤醒线程。
4.3 RunLoop 和 while(1) 死循环有何区别?
- RunLoop 是事件驱动+自动休眠,while(1) 死循环会一直占用 CPU。
4.4 RunLoop 与 autorelease pool
- RunLoop 每次循环结束时会自动释放一次 autorelease pool,避免内存泄漏。
五、代码示例
5.1 子线程定时器
- (void)test2 {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
- (void)newThread {
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
5.2 GCD 定时器
uint64_t interval = 0.01 * NSEC_PER_SEC;
dispatch_queue_t queue = dispatch_queue_create("my_queue", 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD定时器触发");
});
dispatch_resume(timer);
5.3 主线程定时器加到 CommonModes
self.timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
5.4 主线程 RunLoop 启动
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
六、总结
- 每个线程都有一个 RunLoop 实例,主线程的 RunLoop 在 UIApplication 启动时自动启动。
- RunLoop 让线程能够持续监听事件(如触摸、网络、定时器),实现“随时待命”的效果。
- RunLoop 只处理两类事件源:输入源(Input Source) 和 定时源(Timer Source)。
- 子线程如需事件循环,需手动启动 RunLoop 并添加事件源。
参考资料
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。