RunLoop 深度探索:线程为什么不会自动退出

引言

在 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"的效果。


参考资料

  1. CFRunLoop.c 源码
  2. Apple Documentation: Run Loops
  3. 深入理解 RunLoop
相关推荐
2501_915918412 小时前
Flutter 加固方案全解析,从 Dart 层到 IPA 成品的多工具协同防护体系
flutter·macos·ios·小程序·uni-app·cocoa·iphone
碧水澜庭3 小时前
Mac升级集成Gemini的Chrome浏览器完整指南
chrome·macos·ai编程
Digitally3 小时前
如何将照片从 Mac 传输到 Android
android·macos
AirDroid_cn4 小时前
iPhone放大镜跟随模式下,画面抖动,如何稳定?
macos·ios·objective-c·cocoa·iphone·ipad
AirDroid_cn4 小时前
iPhone 新安装的APP无法调用摄像头,如何重置权限?
macos·ios·objective-c·cocoa·iphone
私人珍藏库4 小时前
[吾爱大神原创工具] 【2025-12-03更新】【免越狱】iOS任意版本号APP下载v8.1
macos·ios·cocoa
w1wi5 小时前
【环境部署】MacOS安装Tomcat
java·macos·tomcat
习惯就好zz5 小时前
修复 macOS 下 Godot-CPP 链接丢失 TLS 符号 (__ZTW) 问题记录
macos·godot·clang·godot-cpp
zhimingwen6 小时前
macOS 上安装与配置apksigner (读取APK签名的工具)
android·macos· sha-1