RunLoop 深度解析

目录

  1. RunLoop 基础概念
  2. RunLoop 底层原理
  3. RunLoop 源码分析
  4. RunLoop 的实际应用
  5. 常见应用场景
  6. 最佳实践
  7. 实际代码示例

1. RunLoop 基础概念

1.1 什么是 RunLoop

RunLoop(运行循环)是 iOS/macOS 开发中的一个核心概念,它是一个事件处理循环,用于管理和调度线程上的任务。

核心作用:

  • 保持线程存活,避免线程执行完任务后立即退出
  • 处理各种事件(触摸事件、定时器、网络请求等)
  • 在没有事件时让线程休眠,节省 CPU 资源
  • 在有事件时唤醒线程,及时处理

1.2 RunLoop 与线程的关系

重要特性:

  • 一对一关系:每个线程都有且仅有一个 RunLoop
  • 懒加载:RunLoop 不会自动创建,需要时才会创建
  • 主线程 RunLoop:应用启动时自动创建并运行
  • 子线程 RunLoop:默认不创建,需要手动获取或创建
// 获取当前线程的 RunLoop(如果不存在会自动创建)
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
// 获取主线程的 RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];

1.3 RunLoop 的基本结构

RunLoop 包含以下几个核心组件:

1.3.1 Mode(模式)

RunLoop 运行在特定的 Mode 下,不同 Mode 包含不同的 Source、Timer 和 Observer。

常见的 Mode:

  • NSDefaultRunLoopMode:默认模式,大多数操作在此模式下
  • UITrackingRunLoopMode:UI 追踪模式,ScrollView 滑动时切换到此模式
  • NSRunLoopCommonModes:占位模式,包含 Default 和 Tracking 模式

Mode 切换的意义:

  • 当 ScrollView 滑动时,RunLoop 切换到 UITrackingRunLoopMode
  • 此时 NSDefaultRunLoopMode 下的 Timer 会暂停
  • 只有添加到 NSRunLoopCommonModes 的 Timer 才会继续执行
1.3.2 Source(事件源)

Source 是 RunLoop 要处理的事件源,分为两类:

Source0:

  • 需要手动标记为待处理(signal)
  • 处理 App 内部事件,如触摸事件、performSelector 等
  • 不能主动唤醒 RunLoop

Source1:

  • 基于 mach_port,可以主动唤醒 RunLoop
  • 处理系统级事件,如端口通信、系统信号等
1.3.3 Timer(定时器)

Timer 是基于时间的触发器,必须添加到 RunLoop 才能正常工作。

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
                                         target:self
                                       selector:@selector(timerAction)
                                       userInfo:nil
                                        repeats:YES];
// 添加到 RunLoop
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
1.3.4 Observer(观察者)

Observer 用于监听 RunLoop 的状态变化,可以监控以下时机:

  • RunLoop 即将进入
  • RunLoop 即将处理 Timer
  • RunLoop 即将处理 Source
  • RunLoop 即将进入休眠
  • RunLoop 被唤醒
  • RunLoop 即将退出

1.4 RunLoop 的生命周期

启动 → 进入循环 → 处理事件 → 休眠 → 唤醒 → 处理事件 → ... → 退出

关键点:

  • RunLoop 在没有事件时会进入休眠状态
  • 有事件时会自动唤醒
  • 可以通过 CFRunLoopStop() 主动停止 RunLoop

2. RunLoop 底层原理

2.1 RunLoop 的运行机制

RunLoop 的核心是一个 do-while 循环,不断检查是否有事件需要处理。

伪代码表示:

void CFRunLoopRun() {
int32_t result;
do {
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode,
seconds,
returnAfterSourceHandled);
} while (result != kCFRunLoopRunStopped);
}

运行流程:

  1. 通知 Observer:即将进入 RunLoop
  2. 通知 Observer:即将处理 Timer
  3. 通知 Observer:即将处理 Source0
  4. 处理 Source0
  5. 如果有 Source1 就绪,立即处理
  6. 通知 Observer:线程即将休眠
  7. 休眠,等待被唤醒
    • 端口有消息(Source1)
    • Timer 到时间
    • RunLoop 超时
    • 被手动唤醒
  8. 通知 Observer:线程被唤醒
  9. 处理唤醒时收到的事件
    • 如果是 Timer,处理 Timer 回调
    • 如果是 dispatch 到 main queue 的 block,执行 block
    • 如果是 Source1,处理 Source1
  10. 根据结果决定是否继续循环
    • 如果处理了事件,回到步骤 2
    • 如果超时,回到步骤 2
    • 如果被停止,退出循环

2.2 Mode 切换机制

RunLoop 在同一时间只能运行在一个 Mode 下,切换 Mode 需要退出当前循环,重新以新 Mode 进入。

Mode 切换的触发时机:

  • ScrollView 开始滑动:切换到 UITrackingRunLoopMode
  • ScrollView 停止滑动:切换回 NSDefaultRunLoopMode
  • 系统事件:可能切换到其他 Mode

为什么需要 Mode 切换?

  • 隔离不同场景的事件处理
  • 避免滑动时 Timer 影响滚动性能
  • 提供更精细的事件控制

2.3 Source 的分类和处理

Source0(非基于 Port)

特点:

  • 需要手动标记为待处理状态
  • 不能主动唤醒 RunLoop
  • 处理 App 内部事件

处理流程:

  1. 事件发生(如触摸事件)
  2. 系统标记 Source0 为待处理
  3. RunLoop 被其他方式唤醒(如 Source1)
  4. RunLoop 检查并处理所有待处理的 Source0
  5. 调用 Source0 的回调函数
Source1(基于 Port)

特点:

  • 基于 mach_port(进程间通信端口)
  • 可以主动唤醒 RunLoop
  • 处理系统级事件

处理流程:

  1. 系统事件发生(如端口收到消息)
  2. 内核通过 mach_port 发送消息
  3. Source1 接收到消息,唤醒 RunLoop
  4. RunLoop 处理 Source1 事件
  5. 调用 Source1 的回调函数

2.4 Timer 的实现原理

Timer 在底层是基于 mach_absolute_time() 实现的,精度很高。

Timer 的工作机制:

  1. 创建 Timer:设置时间间隔和回调
  2. 添加到 RunLoop:Timer 被添加到当前 Mode 的 Timer 列表
  3. RunLoop 检查:每次循环检查 Timer 是否到期
  4. 触发回调:如果到期,调用 Timer 的回调方法
  5. 重复执行:如果是重复 Timer,重新计算下次触发时间

Timer 的精度问题:

// Timer 不是实时的,会受到 RunLoop 的影响
// 如果 RunLoop 正在处理耗时操作,Timer 可能延迟触发
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                   target:self
                                                 selector:@selector(timerAction)
                                                 userInfo:nil
                                                  repeats:YES];
// 添加到 CommonModes 可以避免滑动时暂停
[[NSRunLoop currentRunLoop] addTimer:timer
                              forMode:NSRunLoopCommonModes];

2.5 Observer 的监控机制

Observer 使用回调函数监听 RunLoop 的状态变化。

可监控的状态:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),              // 即将进入 RunLoop
kCFRunLoopBeforeTimers = (1UL << 1),       // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2),      // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5),     // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),      // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),               // 即将退出 RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU     // 所有状态
};

Observer 的应用:

  • 性能监控:检测主线程卡顿
  • 自动释放池:在合适的时机释放对象
  • 日志记录:跟踪 RunLoop 的运行状态

2.6 RunLoop 的休眠机制

RunLoop 的休眠不是简单的 sleep(),而是基于 mach_msg() 的休眠。

休眠原理:

  1. RunLoop 调用 mach_msg() 等待消息
  2. 如果没有消息,线程进入内核态休眠
  3. 有消息时,内核唤醒线程
  4. 线程回到用户态,继续执行

优势:

  • 真正的休眠,不占用 CPU 资源
  • 可以被精确唤醒
  • 响应速度快

3. RunLoop 源码分析

3.1 CFRunLoopRef 结构体解析

CFRunLoop 在底层是一个结构体,包含以下关键成员:

struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock;              // 锁,保证线程安全
__CFPort _wakeUpPort;               // 用于唤醒 RunLoop 的端口
Boolean _stopped;                   // 是否已停止
Boolean _ignoreWakeUps;             // 是否忽略唤醒
CFMutableSetRef _commonModes;       // 存储所有 common modes
CFMutableSetRef _commonModeItems;   // 存储所有 common mode items
CFRunLoopModeRef _currentMode;      // 当前运行的 mode
CFMutableSetRef _modes;             // 存储所有 modes
struct _block_item *_blocks_head;   // 待执行的 block 链表头
struct _block_item *_blocks_tail;   // 待执行的 block 链表尾
CFAbsoluteTime _runTime;            // RunLoop 开始运行的时间
CFAbsoluteTime _sleepTime;          // RunLoop 进入休眠的时间
CFTypeRef _counterpart;             // 对应的 NSRunLoop
};

关键字段说明:

  • _currentMode:当前运行的 Mode,RunLoop 一次只能运行在一个 Mode 下
  • _modes:所有注册的 Mode 集合
  • _commonModes:所有标记为 common 的 Mode 名称集合
  • _commonModeItems:所有添加到 common modes 的 items(Source、Timer、Observer)

3.2 CFRunLoopMode 结构体解析

每个 Mode 包含该 Mode 下的所有 Source、Timer 和 Observer:

struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFStringRef _name;                  // Mode 名称,如 "kCFRunLoopDefaultMode"
Boolean _stopped;
CFMutableSetRef _sources0;          // Source0 集合
CFMutableSetRef _sources1;          // Source1 集合
CFMutableArrayRef _observers;       // Observer 数组
CFMutableArrayRef _timers;          // Timer 数组
CFMutableDictionaryRef _portToV1SourceMap;  // port 到 Source1 的映射
__CFPortSet _portSet;               // 所有 port 的集合
CFIndex _observerMask;              // Observer 的掩码
uint64_t _timerSoftDeadline;        // Timer 软截止时间
uint64_t _timerHardDeadline;        // Timer 硬截止时间
};

关键字段说明:

  • _sources0_sources1:分别存储两种类型的 Source
  • _timers:存储该 Mode 下的所有 Timer
  • _observers:存储该 Mode 下的所有 Observer
  • _portToV1SourceMap:用于快速查找 port 对应的 Source1

3.3 Source/Timer/Observer 的数据结构

CFRunLoopSource
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;                     // 标志位
pthread_mutex_t _lock;
CFIndex _order;                     // 优先级
CFMutableBagRef _runLoops;          // 该 Source 被添加到哪些 RunLoop
union {
CFRunLoopSourceContext version0; // Source0 的上下文
CFRunLoopSourceContext1 version1; // Source1 的上下文
} _context;
};

Source0 和 Source1 的区别:

  • Source0:_context.version0 包含回调函数,需要手动标记为待处理
  • Source1:_context.version1 包含 mach_port,可以主动唤醒 RunLoop
CFRunLoopTimer
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;                     // 标志位
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;              // 所属的 RunLoop
CFMutableSetRef _rlModes;           // 添加到哪些 modes
CFAbsoluteTime _nextFireDate;       // 下次触发时间
CFTimeInterval _interval;           // 时间间隔
CFTimeInterval _tolerance;          // 容差
uint64_t _fireTSR;                  // 触发时间戳
CFIndex _order;                     // 优先级
CFRunLoopTimerCallBack _callout;    // 回调函数
CFRunLoopTimerContext _context;    // 上下文
};
CFRunLoopObserver
struct __CFRunLoopObserver {
CFRuntimeBase _base;
uint32_t _bits;                     // 标志位
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;              // 所属的 RunLoop
CFMutableSetRef _rlModes;           // 添加到哪些 modes
CFOptionFlags _activities;          // 监听哪些活动
CFIndex _order;                     // 优先级
CFRunLoopObserverCallBack _callout; // 回调函数
CFRunLoopObserverContext _context;  // 上下文
};

3.4 关键函数实现分析

CFRunLoopRun
void CFRunLoopRun(void) {
int32_t result;
do {
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode,
1.0e10,  // 超时时间(几乎无限)
true);   // returnAfterSourceHandled
} while (result != kCFRunLoopRunStopped);
}

说明:

  • 不断调用 CFRunLoopRunInMode,直到 RunLoop 被停止
  • 使用 kCFRunLoopDefaultMode 作为默认 Mode
  • 超时时间设置为很大,几乎不会超时
CFRunLoopRunInMode

这是 RunLoop 的核心函数,主要逻辑:

SInt32 CFRunLoopRunInMode(CFStringRef modeName,
CFTimeInterval seconds,
Boolean returnAfterSourceHandled) {
// 1. 获取或创建 RunLoop
CFRunLoopRef rl = CFRunLoopGetCurrent();
// 2. 根据 modeName 查找对应的 Mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 3. 如果 Mode 不存在,返回错误
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
return kCFRunLoopRunFinished;
}
// 4. 设置当前 Mode
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
// 5. 通知 Observer:即将进入 RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 6. 进入主循环
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 7. 恢复之前的 Mode
rl->_currentMode = previousMode;
// 8. 通知 Observer:即将退出 RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
__CFRunLoopRun(核心循环)

这是 RunLoop 真正执行循环的地方:

static int32_t __CFRunLoopRun(CFRunLoopRef rl,
CFRunLoopModeRef rlm,
CFTimeInterval seconds,
Boolean stopAfterHandle,
CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 1. 通知 Observer:即将处理 Timer
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 2. 通知 Observer:即将处理 Source
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 3. 处理 Source0
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 4. 检查是否有 Source1 就绪
Boolean hasSource1 = __CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm);
if (hasSource1) {
// 5. 处理 Source1
__CFRunLoopDoSource1(rl, rlm, msg, msg->msgh_size, &reply);
}
// 6. 处理主队列的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
// 7. 通知 Observer:即将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 8. 进入休眠,等待消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
// 9. 通知 Observer:被唤醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 10. 处理唤醒时收到的事件
if (MACH_PORT_NULL != livePort) {
// 处理 Source1
__CFRunLoopDoSource1(rl, rlm, msg, msg->msgh_size, &reply);
} else if (timers_all_fired) {
// 处理 Timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
// 11. 处理主队列的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
// 12. 检查是否需要退出
if (__CFRunLoopIsStopped(rl)) {
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}

3.5 Common Modes 的实现机制

Common Modes 是一个特殊的概念,它不是一个真正的 Mode,而是一个占位符。

实现原理:

  1. NSRunLoopCommonModes 是一个字符串数组,包含多个 Mode 名称
  2. 当向 Common Modes 添加 item 时,实际上会添加到所有 common modes 中
  3. 当切换 Mode 时,common mode items 会自动跟随切换

源码中的处理:

// 添加 item 到 common modes
void CFRunLoopAddItemToCommonModes(CFRunLoopRef rl, CFRunLoopModeItemRef item) {
// 遍历所有 common modes
CFSetApplyFunction(rl->_commonModes, ^(const void *value, void *context) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopModeRef mode = __CFRunLoopFindMode(rl, modeName, false);
if (mode) {
// 将 item 添加到该 mode
__CFRunLoopModeAddItem(mode, item);
}
}, NULL);
}

3.6 自动释放池与 RunLoop

自动释放池的创建和释放与 RunLoop 密切相关:

时机:

  • kCFRunLoopEntry 时创建自动释放池
  • kCFRunLoopBeforeWaiting 时释放旧的池并创建新池
  • kCFRunLoopExit 时释放自动释放池

源码实现:

// 在 RunLoop 进入时
if (kCFRunLoopEntry == activity) {
_objc_autoreleasePoolPush();
}
// 在 RunLoop 即将休眠时
if (kCFRunLoopBeforeWaiting == activity) {
_objc_autoreleasePoolPop();
_objc_autoreleasePoolPush();
}
// 在 RunLoop 退出时
if (kCFRunLoopExit == activity) {
_objc_autoreleasePoolPop();
}

这样设计的好处是:

  • 在一次 RunLoop 循环中创建的对象,在循环结束时统一释放
  • 避免内存峰值过高
  • 及时释放不需要的对象

4. RunLoop 的实际应用

4.1 主线程 RunLoop 与 UI 渲染

主线程的 RunLoop 是 iOS 应用的核心,负责处理所有 UI 相关的事件。

主线程 RunLoop 的工作流程:

  1. 触摸事件处理

    • 用户触摸屏幕 → Source1 接收事件 → 唤醒 RunLoop
    • RunLoop 处理触摸事件 → 调用 UIResponder 的方法
    • 事件沿着响应链传递
  2. UI 更新

    • 修改 UI 属性(如 frame、backgroundColor)不会立即生效
    • 修改会在当前 RunLoop 循环结束时统一渲染
    • 通过 setNeedsDisplay 标记需要重绘的视图
  3. 布局更新

    • setNeedsLayout 标记需要重新布局
    • 在 RunLoop 的 kCFRunLoopBeforeWaiting 时执行布局计算
    • 然后执行绘制操作

代码示例:

// 修改视图属性
self.view.backgroundColor = [UIColor redColor];
// 此时视图不会立即更新,而是等到 RunLoop 休眠前统一更新
// 强制立即更新(不推荐,会阻塞主线程)
[self.view layoutIfNeeded];

4.2 子线程中创建 RunLoop

默认情况下,子线程没有 RunLoop,线程执行完任务后就会退出。如果需要让子线程保持存活并处理任务,需要手动创建并运行 RunLoop。

创建常驻线程的步骤:

  1. 创建线程
  2. 在线程中获取 RunLoop(会自动创建)
  3. 添加 Source 或 Timer 到 RunLoop(否则 RunLoop 会立即退出)
  4. 运行 RunLoop

代码示例:

// 创建常驻线程
@property (nonatomic, strong) NSThread *backgroundThread;
- (void)createBackgroundThread {
    self.backgroundThread = [[NSThread alloc] initWithTarget:self
                                                     selector:@selector(threadEntry)
                                                       object:nil];
    [self.backgroundThread start];
}
- (void)threadEntry {
    @autoreleasepool {
        // 获取当前线程的 RunLoop(会自动创建)
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        // 添加一个 Port 作为 Source,防止 RunLoop 退出
        NSPort *port = [NSPort port];
        [runLoop addPort:port forMode:NSDefaultRunLoopMode];
        // 运行 RunLoop(会一直运行,直到线程退出)
        [runLoop run];
    }
}
// 在常驻线程上执行任务
- (void)performTaskOnBackgroundThread:(void (^)(void))task {
    [self performSelector:@selector(executeTask:)
                 onThread:self.backgroundThread
               withObject:task
            waitUntilDone:NO];
}
- (void)executeTask:(void (^)(void))task {
    if (task) {
        task();
    }
}

4.3 使用 RunLoop 实现常驻线程

常驻线程常用于后台任务处理,如网络请求、文件操作等。

应用场景:

  • 后台下载任务
  • 定时任务执行
  • 数据同步
  • 日志记录

注意事项:

  • 必须添加 Source 或 Timer,否则 RunLoop 会立即退出
  • 使用 Port 是最简单的方式
  • 线程退出前要停止 RunLoop

4.4 NSTimer 与 RunLoop 的关系

NSTimer 必须添加到 RunLoop 才能正常工作,这是很多开发者容易忽略的点。

常见问题:

  1. Timer 不触发

    • 原因:没有添加到 RunLoop 或添加到了错误的 Mode
    • 解决:确保添加到正确的 RunLoop 和 Mode
  2. 滑动时 Timer 暂停

    • 原因:Timer 添加到了 NSDefaultRunLoopMode,滑动时切换到 UITrackingRunLoopMode
    • 解决:添加到 NSRunLoopCommonModes

代码示例:

// 方式1:使用 scheduledTimerWithTimeInterval(自动添加到当前 RunLoop 的 DefaultMode)
NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                    target:self
                                                  selector:@selector(timerAction)
                                                  userInfo:nil
                                                   repeats:YES];
// 方式2:手动创建并添加到 RunLoop
NSTimer *timer2 = [NSTimer timerWithTimeInterval:1.0
                                          target:self
                                        selector:@selector(timerAction)
                                        userInfo:nil
                                         repeats:YES];
// 添加到 DefaultMode(滑动时会暂停)
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSDefaultRunLoopMode];
// 添加到 CommonModes(滑动时不会暂停)
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSRunLoopCommonModes];
// 方式3:使用 GCD Timer(不依赖 RunLoop,更精确)
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
    // 定时任务
});
dispatch_resume(timer);

4.5 网络请求与 RunLoop

NSURLConnection 的代理方法默认在创建连接的线程的 RunLoop 中回调。

问题场景:

  • 在子线程中创建 NSURLConnection
  • 子线程没有运行 RunLoop
  • 代理方法不会被调用

解决方案:

// 方式1:在主线程创建连接
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                               delegate:self
                                                       startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
                      forMode:NSDefaultRunLoopMode];
[connection start];
// 方式2:在子线程运行 RunLoop
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                                   delegate:self
                                                           startImmediately:NO];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [connection scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
    [connection start];
    [runLoop run]; // 运行 RunLoop,等待回调
});
// 方式3:使用 NSURLSession(推荐,不依赖 RunLoop)
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
                                         completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    // 处理响应
}];
[task resume];

4.6 事件响应链与 RunLoop

触摸事件的处理流程与 RunLoop 密切相关:

  1. 事件产生:用户触摸屏幕
  2. 系统捕获:IOKit 捕获触摸事件
  3. 事件传递:通过 Source1 传递给主线程 RunLoop
  4. RunLoop 唤醒:RunLoop 被唤醒
  5. 事件分发:RunLoop 处理 Source1,调用 UIApplication 的 sendEvent:
  6. 响应链传递:事件沿着响应链传递(UIApplication → UIWindow → ViewController → View)
  7. 处理事件:找到合适的响应者处理事件

代码示例:

// 重写 hitTest:withEvent: 自定义事件响应
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 检查是否在视图范围内
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
    // 检查子视图
    for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
        CGPoint convertedPoint = [self convertPoint:point toView:subview];
        UIView *hitView = [subview hitTest:convertedPoint withEvent:event];
        if (hitView) {
            return hitView;
        }
    }
    return self;
}
// 重写 touchesBegan:withEvent: 处理触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 处理触摸开始
}

4.7 performSelector 与 RunLoop

performSelector:onThread:withObject:waitUntilDone: 依赖于目标线程的 RunLoop。

工作原理:

  • 在目标线程的 RunLoop 中添加一个 Source0
  • 当 RunLoop 运行时,会处理这个 Source0
  • 调用指定的 selector

注意事项:

  • 目标线程必须有运行中的 RunLoop
  • waitUntilDone:YES 会阻塞当前线程
  • waitUntilDone:NO 不会阻塞,但需要目标线程的 RunLoop 运行

代码示例:

// 在主线程执行(waitUntilDone:NO,不阻塞)
[self performSelectorOnMainThread:@selector(updateUI)
                       withObject:nil
                    waitUntilDone:NO];
// 在指定线程执行
[self performSelector:@selector(doWork)
             onThread:self.backgroundThread
           withObject:nil
        waitUntilDone:NO];
// 延迟执行(依赖于当前线程的 RunLoop)
[self performSelector:@selector(delayedAction)
           withObject:nil
           afterDelay:2.0];
// 取消延迟执行
[NSObject cancelPreviousPerformRequestsWithTarget:self
                                         selector:@selector(delayedAction)
                                           object:nil];

5. 常见应用场景

5.1 性能监控(卡顿检测)

通过监控 RunLoop 的状态,可以检测主线程的卡顿情况。

原理:

  • 添加 Observer 监听 RunLoop 的状态
  • 记录 kCFRunLoopBeforeWaitingkCFRunLoopAfterWaiting 的时间间隔
  • 如果间隔过长,说明主线程被阻塞

实现代码:

@interface RunLoopMonitor : NSObject
@property (nonatomic, assign) BOOL isMonitoring;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) CFRunLoopActivity activity;
@end
@implementation RunLoopMonitor
+ (instancetype)sharedInstance {
    static RunLoopMonitor *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[RunLoopMonitor alloc] init];
    });
    return instance;
}
- (void)startMonitoring {
    if (self.isMonitoring) {
        return;
    }
    self.isMonitoring = YES;
    self.semaphore = dispatch_semaphore_create(0);
    // 创建 Observer
    CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                           kCFRunLoopAllActivities,
                                                           YES,
                                                           0,
                                                           &runLoopObserverCallBack,
                                                           &context);
    // 添加到主线程 RunLoop
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 在子线程中监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (self.isMonitoring) {
            // 等待 50ms,如果超时说明主线程可能卡顿
            long st = dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
            if (st != 0) {
                if (!self.semaphore) {
                    self.isMonitoring = NO;
                    self.activity = 0;
                    break;
                }
                // 检查 RunLoop 状态
                if (self.activity == kCFRunLoopBeforeSources ||
                    self.activity == kCFRunLoopAfterWaiting) {
                    // 可能发生卡顿,记录堆栈信息
                    [self logStackInfo];
                }
            }
        }
    });
}
static void runLoopObserverCallBack(CFRunLoopObserverRef observer,
                                    CFRunLoopActivity activity,
                                    void *info) {
    RunLoopMonitor *monitor = (__bridge RunLoopMonitor *)info;
    monitor.activity = activity;
    dispatch_semaphore_signal(monitor.semaphore);
}
- (void)logStackInfo {
    NSArray *callStack = [NSThread callStackSymbols];
    NSLog(@"主线程可能卡顿,堆栈信息:\n%@", callStack);
}
- (void)stopMonitoring {
    self.isMonitoring = NO;
    self.semaphore = nil;
}
@end

5.2 自动释放池的时机

了解 RunLoop 与自动释放池的关系,有助于优化内存管理。

自动释放池的创建和释放时机:

  • kCFRunLoopEntry:创建自动释放池
  • kCFRunLoopBeforeWaiting:释放旧池,创建新池
  • kCFRunLoopExit:释放自动释放池

优化建议:

  • 在 RunLoop 循环中创建大量临时对象时,考虑手动创建 @autoreleasepool
  • 避免在单次 RunLoop 循环中创建过多对象

代码示例:

// 在循环中处理大量数据时,使用 @autoreleasepool
for (int i = 0; i < 10000; i++) {
    @autoreleasepool {
        // 创建临时对象
        NSArray *tempArray = [self processData:i];
        // 使用 tempArray
    }
    // tempArray 在这里已经被释放
}

5.3 图片加载优化

利用 RunLoop 的特性,可以在合适的时机加载图片,避免阻塞主线程。

优化策略:

  • kCFRunLoopBeforeWaiting 时加载图片
  • 避免在用户交互时加载图片
  • 使用 Observer 监控 RunLoop 状态

代码示例:

@interface ImageLoader : NSObject
@property (nonatomic, strong) NSMutableArray *imageLoadTasks;
@end
@implementation ImageLoader
- (void)setupRunLoopObserver {
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(
        kCFAllocatorDefault,
        kCFRunLoopBeforeWaiting | kCFRunLoopExit,
        YES,
        0,
        ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            // 在 RunLoop 即将休眠时加载图片
            [self loadImagesIfNeeded];
        });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
}
- (void)loadImagesIfNeeded {
    if (self.imageLoadTasks.count == 0) {
        return;
    }
    // 每次只加载一张图片,避免阻塞
    ImageLoadTask *task = [self.imageLoadTasks firstObject];
    [self.imageLoadTasks removeObjectAtIndex:0];
    // 加载图片
    [self loadImage:task];
}
@end

5.4 网络请求优化

虽然现在推荐使用 NSURLSession,但了解 RunLoop 与网络请求的关系仍然有用。

优化要点:

  • 确保网络请求的回调线程有运行中的 RunLoop
  • 使用 NSURLSession 可以避免 RunLoop 相关问题
  • 在后台线程处理网络请求时,注意 RunLoop 的生命周期

5.5 后台任务处理

使用 RunLoop 实现后台任务的持续处理。

应用场景:

  • 后台数据同步
  • 日志上传
  • 缓存清理

代码示例:

@interface BackgroundTaskManager : NSObject
@property (nonatomic, strong) NSThread *backgroundThread;
@property (nonatomic, strong) NSMutableArray *taskQueue;
@end
@implementation BackgroundTaskManager
- (void)startBackgroundThread {
    self.taskQueue = [NSMutableArray array];
    self.backgroundThread = [[NSThread alloc] initWithTarget:self
                                                     selector:@selector(threadEntry)
                                                       object:nil];
    [self.backgroundThread start];
}
- (void)threadEntry {
    @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        // 添加 Port,保持 RunLoop 运行
        NSPort *port = [NSPort port];
        [runLoop addPort:port forMode:NSDefaultRunLoopMode];
        // 添加 Timer,定期检查任务队列
        NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
                                                 target:self
                                               selector:@selector(processTasks)
                                               userInfo:nil
                                                repeats:YES];
        [runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
- (void)processTasks {
    if (self.taskQueue.count > 0) {
        Task *task = [self.taskQueue firstObject];
        [self.taskQueue removeObjectAtIndex:0];
        [self executeTask:task];
    }
}
- (void)addTask:(Task *)task {
    [self performSelector:@selector(addTaskToQueue:)
                 onThread:self.backgroundThread
               withObject:task
            waitUntilDone:NO];
}
- (void)addTaskToQueue:(Task *)task {
    [self.taskQueue addObject:task];
}
@end

5.6 延迟执行优化

利用 RunLoop 的特性,实现更精确的延迟执行。

场景:

  • 需要在用户停止操作后执行任务
  • 避免频繁执行某些操作
  • 实现防抖(debounce)功能

代码示例:

@interface Debouncer : NSObject
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation Debouncer
- (void)debounce:(void (^)(void))block delay:(NSTimeInterval)delay {
    // 取消之前的 Timer
    [self.timer invalidate];
    // 创建新的 Timer
    self.timer = [NSTimer scheduledTimerWithTimeInterval:delay
                                                  target:self
                                                selector:@selector(executeBlock:)
                                                userInfo:block
                                                 repeats:NO];
    // 添加到 CommonModes,确保滑动时也能执行
    [[NSRunLoop currentRunLoop] addTimer:self.timer
                                  forMode:NSRunLoopCommonModes];
}
- (void)executeBlock:(NSTimer *)timer {
    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
    [self.timer invalidate];
    self.timer = nil;
}
@end
// 使用示例
Debouncer *debouncer = [[Debouncer alloc] init];
[debouncer debounce:^{
    // 用户停止操作 0.5 秒后执行
    NSLog(@"执行搜索");
} delay:0.5];

6. 最佳实践

6.1 何时需要手动管理 RunLoop

需要手动管理的情况:

  • 创建常驻后台线程
  • 在子线程中使用 NSURLConnection(旧 API)
  • 需要在特定时机执行任务
  • 实现自定义的事件处理机制

不需要手动管理的情况:

  • 主线程(系统自动管理)
  • 使用 GCD 和 NSOperationQueue(不依赖 RunLoop)
  • 使用 NSURLSession(不依赖 RunLoop)
  • 大多数日常开发场景

6.2 RunLoop 的常见陷阱和注意事项

陷阱1:Timer 在滑动时暂停

问题:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                   target:self
                                                 selector:@selector(timerAction)
                                                 userInfo:nil
                                                  repeats:YES];
// 滑动 ScrollView 时,Timer 会暂停

解决:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
陷阱2:子线程 RunLoop 立即退出

问题:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop run]; // 立即退出,因为没有 Source 或 Timer
});

解决:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    NSPort *port = [NSPort port];
    [runLoop addPort:port forMode:NSDefaultRunLoopMode];
    [runLoop run]; // 现在会持续运行
});
陷阱3:performSelector 在子线程不执行

问题:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self performSelector:@selector(doWork) withObject:nil afterDelay:1.0];
    // doWork 不会执行,因为子线程的 RunLoop 没有运行
});

解决:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [self performSelector:@selector(doWork) withObject:nil afterDelay:1.0];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
});
陷阱4:RunLoop 无法停止

问题:

// 错误的方式
CFRunLoopStop(CFRunLoopGetCurrent()); // 可能无法立即停止

解决:

// 正确的方式:使用标志位
self.shouldStop = YES;
CFRunLoopStop(CFRunLoopGetCurrent());

6.3 性能优化建议

  1. 避免在主线程 RunLoop 中执行耗时操作

    • 耗时操作会阻塞主线程,导致 UI 卡顿
    • 使用后台线程处理耗时任务
  2. 合理使用 CommonModes

    • 只有需要在滑动时也执行的任务才添加到 CommonModes
    • 避免添加过多任务到 CommonModes,影响滑动性能
  3. 及时移除不需要的 Observer、Timer、Source

    • 避免内存泄漏
    • 减少 RunLoop 的负担
  4. 使用 GCD 替代 RunLoop(如果可能)

    • GCD 更简单,性能更好
    • 不依赖 RunLoop,更可靠

6.4 调试技巧

  1. 打印 RunLoop 信息
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSLog(@"Current RunLoop: %@", runLoop);
NSLog(@"Current Mode: %@", CFRunLoopCopyCurrentMode(runLoop));
  1. 监控 RunLoop 状态
// 添加 Observer 监控所有状态变化
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(
    kCFAllocatorDefault,
    kCFRunLoopAllActivities,
    YES,
    0,
    ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop 进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理 Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理 Source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"被唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop 退出");
                break;
        }
    });
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
  1. 检查 Timer 是否正确添加
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSLog(@"RunLoop modes: %@", [runLoop currentMode]);

7. 实际代码示例

7.1 创建常驻线程的完整示例

// .h 文件
@interface PersistentThread : NSObject
- (void)start;
- (void)stop;
- (void)performTask:(void (^)(void))task;
@end
// .m 文件
@interface PersistentThread ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, strong) NSPort *port;
@property (nonatomic, assign) BOOL shouldStop;
@end
@implementation PersistentThread
- (void)start {
    if (self.thread && self.thread.isExecuting) {
        return;
    }
    self.shouldStop = NO;
    self.thread = [[NSThread alloc] initWithTarget:self
                                           selector:@selector(threadMain)
                                             object:nil];
    [self.thread start];
}
- (void)threadMain {
    @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        // 添加 Port,保持 RunLoop 运行
        self.port = [NSPort port];
        [runLoop addPort:self.port forMode:NSDefaultRunLoopMode];
        // 运行 RunLoop
        while (!self.shouldStop) {
            @autoreleasepool {
                [runLoop runMode:NSDefaultRunLoopMode
                      beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
            }
        }
        // 清理
        [runLoop removePort:self.port forMode:NSDefaultRunLoopMode];
        self.port = nil;
    }
}
- (void)stop {
    self.shouldStop = YES;
    if (self.port) {
        // 通过 Port 发送消息唤醒 RunLoop
        [self.port sendBeforeDate:[NSDate date]
                        components:nil
                              from:nil
                          reserved:0];
    }
}
- (void)performTask:(void (^)(void))task {
    if (!task || !self.thread || !self.thread.isExecuting) {
        return;
    }
    [self performSelector:@selector(executeTask:)
                 onThread:self.thread
               withObject:task
            waitUntilDone:NO];
}
- (void)executeTask:(void (^)(void))task {
    if (task) {
        task();
    }
}
@end

7.2 使用 RunLoop Observer 监控性能

@interface PerformanceMonitor : NSObject
+ (instancetype)sharedInstance;
- (void)startMonitoring;
- (void)stopMonitoring;
@end
@interface PerformanceMonitor ()
@property (nonatomic, assign) BOOL isMonitoring;
@property (nonatomic, strong) CFRunLoopObserverRef observer;
@property (nonatomic, assign) NSTimeInterval lastActivityTime;
@end
@implementation PerformanceMonitor
+ (instancetype)sharedInstance {
    static PerformanceMonitor *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[PerformanceMonitor alloc] init];
    });
    return instance;
}
- (void)startMonitoring {
    if (self.isMonitoring) {
        return;
    }
    self.isMonitoring = YES;
    self.lastActivityTime = CACurrentMediaTime();
    // 创建 Observer
    CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL};
    self.observer = CFRunLoopObserverCreate(
        kCFAllocatorDefault,
        kCFRunLoopAllActivities,
        YES,
        0,
        &runLoopObserverCallback,
        &context
    );
    // 添加到主线程 RunLoop
    CFRunLoopAddObserver(CFRunLoopGetMain(), self.observer, kCFRunLoopCommonModes);
}
static void runLoopObserverCallback(CFRunLoopObserverRef observer,
                                    CFRunLoopActivity activity,
                                    void *info) {
    PerformanceMonitor *monitor = (__bridge PerformanceMonitor *)info;
    NSTimeInterval currentTime = CACurrentMediaTime();
    NSTimeInterval interval = currentTime - monitor.lastActivityTime;
    if (interval > 0.016) { // 超过一帧的时间(60fps = 16.67ms)
        NSLog(@"⚠️ 主线程可能卡顿,间隔: %.3f 秒", interval);
    }
    monitor.lastActivityTime = currentTime;
}
- (void)stopMonitoring {
    if (!self.isMonitoring) {
        return;
    }
    self.isMonitoring = NO;
    if (self.observer) {
        CFRunLoopRemoveObserver(CFRunLoopGetMain(), self.observer, kCFRunLoopCommonModes);
        CFRelease(self.observer);
        self.observer = NULL;
    }
}
- (void)dealloc {
    [self stopMonitoring];
}
@end

7.3 自定义 Source 的实现

@interface CustomSource : NSObject
- (void)fire;
@end
@interface CustomSource ()
{
    CFRunLoopSourceRef _source;
    CFRunLoopRef _runLoop;
}
@end
@implementation CustomSource
- (instancetype)init {
    self = [super init];
    if (self) {
        [self setupSource];
    }
    return self;
}
- (void)setupSource {
    // 创建 Source0 的上下文
    CFRunLoopSourceContext context = {0};
    context.info = (__bridge void *)self;
    context.perform = &sourcePerform;
    context.schedule = &sourceSchedule;
    context.cancel = &sourceCancel;
    // 创建 Source
    _source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
}
static void sourcePerform(void *info) {
    CustomSource *source = (__bridge CustomSource *)info;
    NSLog(@"CustomSource 被触发");
    // 执行自定义逻辑
}
static void sourceSchedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    CustomSource *source = (__bridge CustomSource *)info;
    source->_runLoop = rl;
    NSLog(@"CustomSource 被调度到 RunLoop");
}
static void sourceCancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    CustomSource *source = (__bridge CustomSource *)info;
    source->_runLoop = NULL;
    NSLog(@"CustomSource 被取消");
}
- (void)addToRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode {
    CFRunLoopAddSource([runLoop getCFRunLoop], _source, (__bridge CFStringRef)mode);
}
- (void)removeFromRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode {
    CFRunLoopRemoveSource([runLoop getCFRunLoop], _source, (__bridge CFStringRef)mode);
}
- (void)fire {
    // 标记 Source 为待处理
    CFRunLoopSourceSignal(_source);
    // 如果有 RunLoop,唤醒它
    if (_runLoop) {
        CFRunLoopWakeUp(_runLoop);
    }
}
- (void)dealloc {
    if (_source) {
        CFRelease(_source);
    }
}
@end

7.4 常见问题的解决方案

问题1:Timer 在后台不工作
// 解决方案:使用后台任务和本地通知
- (void)setupBackgroundTimer {
    // 注册后台任务
    [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        // 任务过期处理
    }];
    // 使用 NSTimer(在后台有限时间内有效)
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:60.0
                                                      target:self
                                                    selector:@selector(backgroundTask)
                                                    userInfo:nil
                                                     repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
问题2:网络请求在子线程回调失败
// 解决方案:确保子线程的 RunLoop 运行
- (void)networkRequestInBackground {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://api.example.com"]];
        // 使用 NSURLConnection(需要 RunLoop)
        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                                       delegate:self
                                                               startImmediately:NO];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [connection scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
        [connection start];
        // 运行 RunLoop,等待回调
        [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:30.0]];
    });
}
// 更好的方案:使用 NSURLSession(不依赖 RunLoop)
- (void)networkRequestWithSession {
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://api.example.com"]
                                         completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        // 处理响应
    }];
    [task resume];
}
问题3:performSelector 延迟执行不准确
// 问题:performSelector:withObject:afterDelay: 依赖于 RunLoop,可能不准确
[self performSelector:@selector(doSomething) withObject:nil afterDelay:1.0];
// 解决方案1:使用 GCD(更准确)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
                dispatch_get_main_queue(), ^{
    [self doSomething];
});
// 解决方案2:使用 NSTimer(添加到 CommonModes)
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
                                         target:self
                                       selector:@selector(doSomething)
                                       userInfo:nil
                                        repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

参考资料