引言
在 iOS 开发中,你是否思考过这些问题:
- 为什么主线程启动后永远不会退出?
- 为什么
NSTimer能持续触发回调? - 为什么子线程执行完任务后就立即销毁了?
- 如何让一个线程"常驻"?
这些问题的答案都指向同一个核心机制 ------ RunLoop。
本文将从底层源码角度,深入剖析 RunLoop 如何控制线程的生命周期。
一、线程的本质:执行完就退出
首先,我们需要理解线程的本质行为。
1.1 线程的默认生命周期
objc
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1. 线程开始执行");
NSLog(@"2. 当前线程: %@", [NSThread currentThread]);
NSLog(@"3. 线程即将结束");
}];
thread.name = @"TestThread";
[thread start];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
NSLog(@"4. 线程是否已结束: %d", thread.isFinished);
});
}
输出:
1. 线程开始执行
2. 当前线程: <NSThread: 0x...>{number = 6, name = TestThread}
3. 线程即将结束
4. 线程是否已结束: 1
这说明:线程执行完任务后,会自动退出并销毁。
1.2 线程生命周期图示
┌─────────────────────────────────────────────────────────────┐
│ 普通线程生命周期 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [创建] ──▶ [就绪] ──▶ [运行] ──▶ [结束/销毁] │
│ │ │
│ ▼ │
│ 执行任务代码 │
│ │ │
│ ▼ │
│ 任务执行完毕 │
│ │ │
│ ▼ │
│ 线程自动退出 ◀── 这是关键! │
│ │
└─────────────────────────────────────────────────────────────┘
二、RunLoop 如何让线程"活着"
2.1 主线程为什么不会退出?
看一下 iOS 应用的入口函数:
objc
// main.m
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}
UIApplicationMain 内部做了什么?简化版伪代码:
objc
int UIApplicationMain(...) {
// 1. 创建 UIApplication 对象
// 2. 创建 AppDelegate 对象
// 3. 启动主线程的 RunLoop
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
// 这是一个"死循环"!
while (AppIsRunning) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
}
return 0;
}
关键点:UIApplicationMain 函数内部启动了一个 RunLoop,这个 RunLoop 会一直循环,导致函数永远不会返回。
2.2 RunLoop 的核心循环机制
让我们看看 CFRunLoop 的核心源码(简化版):
c
// CFRunLoop.c 核心逻辑
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl,
CFStringRef modeName,
CFTimeInterval seconds,
Boolean returnAfterSourceHandled) {
// 1. 通知 Observers: 即将进入 RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 2. 核心循环
result = __CFRunLoopRun(rl, currentMode, seconds,
returnAfterSourceHandled, previousMode);
// 3. 通知 Observers: 即将退出 RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl,
CFRunLoopModeRef rlm,
CFTimeInterval seconds,
Boolean stopAfterHandle,
CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// ============ 这是关键的循环体 ============
// 2.1 通知 Observers: 即将处理 Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 2.2 通知 Observers: 即将处理 Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 2.3 处理 Source0 (非端口事件)
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 2.4 如果有 Source1 (端口事件) 待处理,跳转处理
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer),
&livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
// 2.5 通知 Observers: 即将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// ★★★ 2.6 核心:线程进入休眠,等待被唤醒 ★★★
__CFRunLoopSetSleeping(rl);
mach_msg(msg, MACH_RCV_MSG | MACH_RCV_LARGE,
0, msg->msgh_size, port,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
__CFRunLoopUnsetSleeping(rl);
// 2.7 通知 Observers: 刚从休眠中唤醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
// 2.8 处理唤醒事件
if (被 Timer 唤醒) {
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
} else if (被 GCD 唤醒) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else if (被 Source1 唤醒) {
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
}
// 2.9 判断是否需要退出循环
if (被外部强制停止) retVal = kCFRunLoopRunStopped;
if (超时) retVal = kCFRunLoopRunTimedOut;
if (没有 Sources/Timers) retVal = kCFRunLoopRunFinished;
} while (retVal == 0); // ◀── 关键:只要 retVal 为 0,就一直循环
return retVal;
}
2.3 RunLoop 循环流程图
┌──────────────────────────────────────────────────────────────────┐
│ RunLoop 核心循环 │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ 1. 通知 Observer: │
│ kCFRunLoopEntry │
└───────────┬────────────┘
│
┌───────────────────────┼───────────────────────┐
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 2. 处理 Timers/Sources │ │
│ └───────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 3. 通知 Observer: │ │
│ │ BeforeWaiting │ │
│ └───────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ 4. ★ mach_msg() 休眠等待 ★ │ │
│ │ 线程在此处阻塞,不消耗 CPU │ │
│ │ 等待:Timer/Source/Port │ │
│ └────────────────┬────────────────┘ │
│ │ │
│ ◀─ 被唤醒 ─▶ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 5. 通知 Observer: │ │
│ │ AfterWaiting │ │
│ └───────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 6. 处理唤醒事件 │ │
│ │ Timer/GCD/Source1 │ │
│ └───────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 7. 判断是否退出循环 │ │
│ └───────────┬────────────┘ │
│ │ │
│ NO ┌──────────┴──────────┐ YES │
│ │ │ │
└────────────┘ ▼ │
┌────────────────────────┐
│ 8. 通知 Observer: │
│ kCFRunLoopExit │
└────────────────────────┘
│
▼
RunLoop 退出
三、核心机制:mach_msg 系统调用
3.1 什么是 mach_msg?
mach_msg 是 macOS/iOS 内核提供的系统调用,用于进程/线程间通信。
c
mach_msg_return_t mach_msg(
mach_msg_header_t *msg, // 消息缓冲区
mach_msg_option_t option, // 发送/接收选项
mach_msg_size_t send_size, // 发送大小
mach_msg_size_t rcv_size, // 接收大小
mach_port_t rcv_name, // 接收端口
mach_msg_timeout_t timeout, // 超时时间
mach_port_t notify // 通知端口
);
3.2 线程休眠的本质
当 RunLoop 调用 mach_msg 时:
┌──────────────────────────────────────────────────────────┐
│ 用户空间 │
├──────────────────────────────────────────────────────────┤
│ │
│ RunLoop 调用 mach_msg(MACH_RCV_MSG, ...) │
│ │ │
│ │ 系统调用 │
│ ▼ │
├──────────────────────────────────────────────────────────┤
│ 内核空间 │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. 检查端口队列是否有消息 │
│ │ │
│ ├── 有消息 ──▶ 立即返回,继续执行 │
│ │ │
│ └── 无消息 ──▶ 将线程状态设为"等待" │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 线程被挂起 (休眠) │ │
│ │ 不占用 CPU 时间片 │ │
│ │ 等待消息到达 │ │
│ └─────────────────────┘ │
│ │ │
│ 消息到达 │
│ │ │
│ ▼ │
│ 内核唤醒线程 │
│ mach_msg 返回 │
│ │
└──────────────────────────────────────────────────────────┘
这就是 RunLoop 能让线程"活着"但不消耗 CPU 的秘密!
四、为什么 RunLoop 不会退出?
4.1 退出条件分析
从源码可以看到,RunLoop 退出需要满足以下条件之一:
c
// 判断是否退出循环
if (被外部强制停止) {
retVal = kCFRunLoopRunStopped; // CFRunLoopStop() 被调用
}
if (超时) {
retVal = kCFRunLoopRunTimedOut; // 超过指定时间
}
if (mode 中没有 Sources/Timers) {
retVal = kCFRunLoopRunFinished; // 没有任何事件源
}
4.2 主线程 RunLoop 为什么不退出
主线程的 RunLoop 默认配置了多个 Source:
objc
- (void)printMainRunLoopInfo {
CFRunLoopRef mainLoop = CFRunLoopGetMain();
NSLog(@"Main RunLoop: %@", mainLoop);
}
打印输出会显示主线程 RunLoop 包含:
common modes = {
UITrackingRunLoopMode,
kCFRunLoopDefaultMode
}
common mode items = {
// Source0: 触摸事件、PerformSelector
CFRunLoopSource {order = -1, callout = __handleEventQueue}
CFRunLoopSource {order = -1, callout = __handleHIDEventFetcherDrain}
// Source1: 基于 Port 的线程间通信
CFRunLoopSource {order = 0, callout = __IOHIDEventSystemClientQueueCallback}
// Timer: CADisplayLink、NSTimer
CFRunLoopTimer {callout = _ZN2CA7Display15DisplayLinkItem8dispatchEv}
// Observer: AutoreleasePool、UI 更新
CFRunLoopObserver {callout = _wrapRunLoopWithAutoreleasePoolHandler}
CFRunLoopObserver {callout = _ZN2CA11Transaction17observer_callbackEP...}
}
因为主线程 RunLoop 始终有 Source/Timer/Observer,所以永远不会因"没有事件源"而退出。
五、子线程如何保活?
5.1 错误示范:RunLoop 立即退出
objc
- (void)testRunLoopWithoutSource {
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"线程开始");
// 启动 RunLoop,但没有添加任何 Source
[[NSRunLoop currentRunLoop] run];
// ⚠️ 这行代码会立即执行!因为 RunLoop 没有事件源,直接退出
NSLog(@"线程结束");
}];
[self.thread start];
}
5.2 正确做法:添加事件源
方法一:添加 Port
objc
- (void)testRunLoopWithPort {
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"线程开始");
// 添加一个 Port 作为 Source
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port]
forMode:NSDefaultRunLoopMode];
// 启动 RunLoop
[[NSRunLoop currentRunLoop] run];
// 这行代码不会执行,除非手动停止 RunLoop
NSLog(@"线程结束");
}];
self.thread.name = @"KeepAliveThread";
[self.thread start];
}
方法二:添加 Timer
objc
- (void)testRunLoopWithTimer {
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"线程开始");
// 添加一个远期 Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:MAXFLOAT
target:self
selector:@selector(doNothing)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"线程结束");
}];
[self.thread start];
}
方法三:使用 CFRunLoopSourceRef(推荐)
objc
@interface ThreadKeepAlive : NSObject
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) CFRunLoopSourceRef source;
@property (nonatomic, assign) BOOL shouldStop;
@end
@implementation ThreadKeepAlive
- (void)start {
self.shouldStop = NO;
self.thread = [[NSThread alloc] initWithTarget:self
selector:@selector(threadEntryPoint)
object:nil];
[self.thread start];
}
- (void)threadEntryPoint {
@autoreleasepool {
// 创建一个空的 Source
CFRunLoopSourceContext context = {0};
self.source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), self.source, kCFRunLoopDefaultMode);
// 循环运行 RunLoop
while (!self.shouldStop) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, YES);
}
// 清理
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), self.source, kCFRunLoopDefaultMode);
CFRelease(self.source);
self.source = NULL;
NSLog(@"线程正常退出");
}
}
- (void)stop {
if (self.thread == nil) return;
self.shouldStop = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
// 确保在目标线程执行停止操作
[self performSelector:@selector(stopRunLoop)
onThread:self.thread
withObject:nil
waitUntilDone:YES];
}
- (void)stopRunLoop {
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)executeTask:(void(^)(void))task {
if (self.thread == nil || self.thread.isCancelled) return;
[self performSelector:@selector(performTask:)
onThread:self.thread
withObject:task
waitUntilDone:NO];
}
- (void)performTask:(void(^)(void))task {
if (task) task();
}
@end
六、验证实验
6.1 实验:观察 RunLoop 状态变化
objc
- (void)observeRunLoopActivity {
self.thread = [[NSThread alloc] initWithBlock:^{
// 添加 Observer 观察所有状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(
kCFAllocatorDefault,
kCFRunLoopAllActivities, // 观察所有活动
YES, // 重复
0, // 优先级
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"🟢 即将进入 RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"⏰ 即将处理 Timers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"📦 即将处理 Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"😴 即将休眠...");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"⏰ 被唤醒!");
break;
case kCFRunLoopExit:
NSLog(@"🔴 即将退出 RunLoop");
break;
default:
break;
}
}
);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 添加 Source 保活
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port]
forMode:NSDefaultRunLoopMode];
// 添加一个 2 秒后触发的 Timer
[NSTimer scheduledTimerWithTimeInterval:2.0 repeats:NO block:^(NSTimer *timer) {
NSLog(@"🔔 Timer 触发!");
}];
[[NSRunLoop currentRunLoop] run];
}];
[self.thread start];
}
输出:
🟢 即将进入 RunLoop
⏰ 即将处理 Timers
📦 即将处理 Sources
😴 即将休眠... ← 没有事件,进入休眠
⏰ 被唤醒! ← 2秒后被 Timer 唤醒
🔔 Timer 触发!
⏰ 即将处理 Timers
📦 即将处理 Sources
😴 即将休眠... ← 继续休眠,等待下一个事件
七、实际应用场景
7.1 AFNetworking 的常驻线程
AFNetworking 2.x 版本使用常驻线程处理网络回调:
objc
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
7.2 AsyncDisplayKit 的渲染线程
objc
// 创建专门的渲染线程
- (void)setupRenderThread {
self.renderThread = [[NSThread alloc] initWithBlock:^{
CFRunLoopSourceContext context = {0};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
while (!self.shouldStopRenderThread) {
@autoreleasepool {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, YES);
}
}
CFRelease(source);
}];
self.renderThread.name = @"RenderThread";
[self.renderThread start];
}
八、总结
核心要点
┌────────────────────────────────────────────────────────────────┐
│ RunLoop 保活线程核心原理 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. 线程默认行为:执行完代码就退出 │
│ │
│ 2. RunLoop 本质:一个 do-while 循环 │
│ ┌──────────────────────────────────────────────┐ │
│ │ do { │ │
│ │ 处理事件; │ │
│ │ if (没有事件) { │ │
│ │ mach_msg() 休眠; // 不消耗 CPU │ │
│ │ } │ │
│ │ } while (有事件源 && 未被停止); │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ 3. 保活条件:RunLoop 中必须有 Source/Timer/Observer │
│ │
│ 4. 休眠机制:mach_msg 系统调用 │
│ - 用户态 → 内核态 │
│ - 线程被挂起,不占用 CPU │
│ - 有消息时被内核唤醒 │
│ │
│ 5. 退出条件: │
│ - CFRunLoopStop() 被调用 │
│ - 超时 │
│ - Mode 中没有任何 Source/Timer │
│ │
└────────────────────────────────────────────────────────────────┘
一句话总结
RunLoop 通过内部的 do-while 循环 + mach_msg 休眠机制,让线程在有事件时处理,无事件时休眠,从而实现"线程常驻但不消耗 CPU"的效果。
参考资料
- CFRunLoop.c 源码
- Apple Documentation: Run Loops
- 深入理解 RunLoop