Skip to content

02 - Runloop 深度

目录

  1. RunLoop 内核架构
  2. Mode 详解
  3. Source 原理
  4. Timer 原理与精度问题
  5. RunLoop 应用场景
  6. RunLoop 源码级分析
  7. 面试题汇总

1. RunLoop 内核架构

1.1 Core Foundation RunLoop 对象

RunLoop 核心对象(Core Foundation 层级):
┌──────────────────────────────────────────────────────────┐
│                                                              │
│  1. CFRunLoopRef (RunLoop)                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  • modeList:模式列表(Modes)                       │  │
│  │  • currentMode:当前运行的模式                         │  │
│  │  • modes:模式数组                                  │  │
│  │  • observers:观察者列表                              │  │
│  │  • timers:定时器列表                                │  │
│  │  • sources:源列表(Source0/Source1)                │  │
│  │  • port:端口(用于线程间通信)                       │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                              │
│  2. CFRunLoopModeRef (模式)                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  • name:模式名称(NSDefaultRunLoopMode 等)           │  │
│  │  • sources0:Source0(非系统事件)                      │  │
│  │  • sources1:Source1(系统事件/端口)                   │  │
│  │  • timers:定时器列表                                  │  │
│  │  • observers:观察者列表                               │  │
│  │  • port:端口                                          │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                              │
│  3. CFRunLoopSourceRef (源)                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Source0(非系统事件)                                 │  │
│  │  ├─ 回调函数(callback)                              │  │
│  │  ├─ 上下文(context)                                 │  │
│  │  └─ 用于应用层事件(performSelector 等)               │  │
│  │                                                          │  │
│  │  Source1(系统事件/端口)                              │  │
│  │  ├─ mach_port(端口)                                 │  │
│  │  ├─ handler(回调函数)                               │  │
│  │  └─ 用于系统间通信(Mach port)                        │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                              │
│  4. CFRunLoopTimerRef (定时器)                               │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  • interval:间隔时间                                  │  │
│  │  • tolerance:容差(允许的时间偏差)                     │  │
│  │  • handler:回调函数                                   │  │
│  │  • flags:标志(repeating/oneshot)                    │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                              │
│  5. CFRunLoopObserverRef (观察者)                            │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  • activities:活动类型                                │  │
│  │  • order:优先级                                      │  │
│  │  • callout:回调函数                                   │  │
│  │  • context:上下文                                      │  │
│  │                                                      │  │
│  │  activities(按执行顺序):                            │  │
│  │  1. Entry(进入)                                    │  │
│  │  2. BeforeTimers(定时器前)                          │  │
│  │  3. BeforeSources(源前)                            │  │
│  │  4. BeforeWait(等待前)                              │  │
│  │  5. AfterWait(等待后)                              │  │
│  │  6. Exit(退出)                                    │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                              │
└───────────────────────────────────────────────────────────┘
*/

1.2 线程与 RunLoop 的关系

线程与 RunLoop 映射关系:
┌──────────────────────────────────────────────────┐
│                                                              │
│  每个线程(包括主线程)都有一个 RunLoop                      │
│  • 主线程的 RunLoop 由 UIKit 自动创建                        │
│  • 子线程的 RunLoop 需要手动创建                            │
│  • 一个线程只有一个 RunLoop 对象                             │
│  • RunLoop 通过线程局部存储(TLS)保存                       │
│                                                              │
│  访问 RunLoop 的方法:                                       │
│  • [NSRunLoop currentRunLoop]    — 获取当前线程的 RunLoop    │
│  • [NSRunLoop mainRunLoop]      — 获取主线程的 RunLoop      │
│  • CFRunLoopGetCurrent()         — CF 版本                   │
│  • CFRunLoopGetMain()            — 主线程 CF 版本           │
│                                                              │
│  线程 RunLoop 生命周期:                                    │
│  • 主线程:自动创建,运行至程序退出                            │
│  • 子线程:手动创建,RunLoop 运行完即销毁                      │
│  • 当子线程没有事件处理时,RunLoop 会自动退出                  │
│                                                              │
│  主线程 RunLoop 保持运行机制:                               │
│  ┌───────────────────────────────────────────────────────┐
│  │  main()                                                    │
│  │  ↓                                                        │
│  │  UIApplicationMain()                                       │
│  │  ↓                                                        │
│  │  [UIApplication sharedApplication] 自动创建主线程 RunLoop   │
│  │  ↓                                                        │
│  │  [[NSRunLoop currentRunLoop] run] — 永远运行                │
│  │  ↓                                                        │
│  │  (等待事件 → 处理事件 → 等待事件 ...)                        │
│  └───────────────────────────────────────────────────────┘
│                                                              │
└────────────────────────────────────────────────────┘

⚠️ 关键理解:
• RunLoop 不是线程的替身,线程才是 RunLoop 的宿主
• RunLoop 只是线程的一个管理器,负责调度事件和定时器
• 主线程的 RunLoop 由 UIKit 自动管理
• 子线程的 RunLoop 需要手动创建并启动

1.3 RunLoop 模式

RunLoop 的标准模式(Core Foundation 定义):
┌──────────────────────────────────────────────────────────┐
│                                                              │
│  NSDefaultRunLoopMode(kCFRunLoopDefaultMode)              │
│  • 默认模式:普通事件处理                                   │
│  • 包括:UI 事件、定时器、performSelector                   │
│  • 当 UI 滚动时,RunLoop 会切换到 UITrackingRunLoopMode      │
│                                                              │
│  UITrackingRunLoopMode                                           │
│  • UI 滚动模式:保证 UI 流畅                                │
│  • 在 UI 滚动期间,其他模式的任务暂停                        │
│  • UI 事件优先级最高                                         │
│                                                              │
│  NSRunLoopCommonModes(kCFRunLoopCommonModes)              │
│  • 伪模式:不是真正的模式                                    │
│  • 将多个模式归为一组                                        │
│  • 添加到 common modes 的源/定时器在所有 common 模式中运行    │
│                                                              │
│  UIDefaultRunLoopMode = NSDefaultRunLoopMode                  │
│  • UIKit 内部使用                                           │
│                                                              │
│  NSURLSessionConfigurationNSURLSessionTaskOperationMode    │
│  • URLSession 异步回调模式                                  │
│                                                              │
│  GSEventReceiveRunLoopMode                                  │
│  • 底层事件模式                                             │
│                                                              │
└──────────────────────────────────────────────────────────────┘

模式切换时机:
┌────────────────────────────────────────────────────────────┐
│                                                              │
│  正常状态:NSDefaultRunLoopMode                              │
│       │                                                    │
│       │ UI 开始滚动                                        │
│       ▼                                                    │
│  UITrackingRunLoopMode                                     │
│       │                                                    │
│       │ UI 滚动结束                                        │
│       ▼                                                    │
│  NSDefaultRunLoopMode(回到默认模式)                       │
│                                                              │
│  ⚠️ 注意:                                                   │
│  • 模式切换是自动的,开发者无需手动管理                      │
│  • 但可以通过 CFRunLoopAddCommonTimer 将定时器添加到 common modes │
│                                                              │
└──────────────────────────────────────────────────────────┘

2. Mode 详解

2.1 RunLoop 运行循环机制

RunLoop 运行循环(核心机制):
┌──────────────────────────────────────────────────────────┐
│                                                              │
│  核心循环:                                                │
│  ┌────────────────────────────────────────────────────┐  │
│  │  while (!done) {                                  │  │
│  │    1. 通知 Observers:即将进入循环                    │  │
│  │    2. 处理 pending 的 Source0(非系统事件)           │  │
│  │    3. 处理 pending 的 Source1(系统事件/端口)        │  │
│  │    4. 如果没有 Source0/Source1 事件:                │  │
│  │       a. 通知 Observers:即将等待                     │  │
│  │       b. 进入等待,直到有事件或定时器触发              │  │
│  │       c. 通知 Observers:等待唤醒                    │  │
│  │       d. 唤醒后重新处理事件                           │  │
│  │    5. 处理 Timer 事件(如果在等待时间内)              │  │
│  │    6. 通知 Observers:即将退出循环                    │  │
│  │  }                                                 │  │
│  └─────────────────────────────────────────────────┘  │
│                                                              │
│  事件处理优先级(从高到低):                                 │
│  1. Source1(系统事件/端口)— 高优先级                      │
│  2. Source0(非系统事件)— 中优先级                          │
│  3. Timer — 低优先级                                        │
│                                                              │
│  ⚠️ Timer 精度:                                            │
│  • Timer 在指定的 timeInterval 后触发                       │
│  • 但实际触发时间受 Mode 影响                               │
│  • 如果在 UITrackingRunLoopMode 模式下,Timer 不会触发       │
│  • 解决方案:将 Timer 添加到 NSRunLoopCommonModes            │
│                                                              │
└───────────────────────────────────────────────────────────┘
*/

2.2 Source 详解

Source 系统:
┌───────────────────────────────────────────────────────────┐
│                                                              │
│  Source0(非系统事件)                                       │
│  ┌───────────────────────────────────────────────────┐   │
│  │  • 回调机制                                          │   │
│  │  • 用于应用层事件(performSelector、自定义事件)       │   │
│  │  • 手动触发(通过 CFRunLoopSourceSignal)              │   │
│  │  • 不直接绑定端口                                     │   │
│  │                                                  │   │
│  │  触发流程:                                          │   │
│  │  1. 调用 CFRunLoopSourceSignal(source)              │   │
│  │  2. 通知 RunLoop 有事件待处理                          │   │
│  │  3. RunLoop 在下一循环调用 Source0 的 handler        │   │
│  └───────────────────────────────────────────────┘   │
│                                                              │
│  Source1(系统事件/端口)                                    │
│  ┌───────────────────────────────────────────────────┐   │
│  │  • mach_port 机制                                   │   │
│  │  • 用于系统间通信(Mach port)                       │   │
│  │  • 系统自动触发                                      │   │
│  │  • 基于 GCD 的 dispatch 也通过 Source1 实现           │   │
│  │                                                  │   │
│  │  触发流程:                                          │   │
│  │  1. 系统发送消息到 mach_port                        │   │
│  │  2. Source1 收到事件                                │   │
│  │  3. 调用 Source1 的 handler                         │   │
│  │  4. dispatch_async 的 block 通过 Source1 触发         │   │
│  └───────────────────────────────────────────────┘   │
│                                                              │
│  dispatch_async 与 RunLoop 的关系:                        │
│  ┌───────────────────────────────────────────────┐    │
│  │  当 dispatch_async 发送到主线程时:                    │
│  │  1. 创建 GCD Source(Source1)                      │    │
│  │  2. 通过 mach_port 通知 RunLoop                       │
│  │  3. RunLoop 在下一个循环处理 Source                   │
│  │  4. handler 执行 dispatch queue 的 block             │
│  │                                                  │    │
│  │  ⚠️ 如果在主线程的 runLoop 中 dispatch_async:       │
│  │     当前 RunLoop 循环不会执行该 block                    │
│  │     要等到下一个循环才执行                                │
│  │     解决方案:CFRunLoopWakeUp() 或 dispatch_sync  │
│  └─────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────┘
*/

2.3 Timer 精度与容差

Timer 精度问题(经典面试题):
┌──────────────────────────────────────────────────────┐
│                                                              │
│  Timer 触发时间偏差原因:                                   │
│  ┌───────────────────────────────────────────────────┐    │
│  │  1. Mode 切换                                       │    │
│  │     • 在 UITrackingRunLoopMode 模式下 Timer 不触发    │
│  │     • UI 滚动时 Timer 延迟触发                        │
│  │     • 解决方案:添加到 common modes                  │
│  │                                                   │    │
│  │  2. 任务执行耗时                                     │
│  │     • Timer 回调任务耗时 > timeInterval             │
│  │     • Timer 不会重叠执行,等前一个完成后再触发         │
│  │     • 解决方案:使用 CADisplayLink 替代              │
│  │                                                   │    │
│  │  3. RunLoop 唤醒延迟                                 │
│  │     • 等待事件时 RunLoop 进入休眠                     │
│  │     • 从休眠到唤醒有延迟                              │
│  │     • 解决方案:使用 NSTimer.tolerance(iOS 10+)    │
│  │                                                   │    │
│  │  4. 线程阻塞                                         │
│  │     • 主线程被阻塞时 Timer 不会触发                   │
│  │     • 解决方案:不要在主线程执行耗时任务              │
│  └─────────────────────────────────────────────────┘    │
│                                                              │
│  NSTimer.tolerance(iOS 10+):                           │
│  ┌───────────────────────────────────────────────────┐    │
│  │  • tolerance 指定允许的时间偏差                      │
│  │  • 系统利用 tolerance 优化电源消耗                   │
│  │  • 如果 tolerance = 1.0s,系统可以将 Timer 触发      │
│  │    时间提前/延后最多 1 秒                            │
│  │  • 适用于:不要求精确时间的 Timer                     │
│  │  • 示例:NSTimer scheduledTimerWithTimeInterval:     │
│  │      tolerance: executingHandler:                    │
│  └──────────────────────────────────────────────┘    │
│                                                              │
│  Timer 创建方式对比:                                       │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  创建方式                          │  说明                │  │
│  ├──────────────────────────────────────────────────────┤  │
│  │  scheduledTimer:                   │  添加到默认 RunLoop   │  │
│  │  scheduledTimerWithTimeInterval:   │  同上,自定义间隔    │  │
│  │  timerWithTimeInterval:            │  不自动添加           │  │
│  │  CADisplayLink                     │  与屏幕刷新同步       │  │
│  └────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────┘
*/

3. RunLoop 应用场景

3.1 典型应用场景

objc
/*
RunLoop 的典型应用场景:
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  场景 1:NSTimer 在 UI 滚动时不暂停                           │
│  ─────────────────────────────────────────────                │
│                                                              │
│  问题:NSTimer 在 UI 滚动时被暂停                             │
│  原因:滚动时 RunLoop 切换到 UITrackingRunLoopMode            │
│  解决:将 Timer 添加到 common modes                           │
│                                                              │
│  // 方案 1:添加到 common modes                              │
│  [timer runLoop: [NSRunLoop currentRunLoop]                 │
│              forMode: NSRunLoopCommonModes];                  │
│                                                              │
│  // 方案 2:使用 CADisplayLink(与屏幕刷新同步)              │
│  CADisplayLink *link = [CADisplayLink                       │
│      displayLinkWithTarget:self                               │
│      selector:@selector(update:)];                          │
│  [link addToRunLoop:[NSRunLoop currentRunLoop]                │
│      forMode: NSRunLoopCommonModes];                          │
│                                                              │
│  ─────────────────────────────────────────────                │
│  场景 2:异步操作在主线程回调                                 │
│  ─────────────────────────────────────────────                │
│                                                              │
│  dispatch_async(dispatch_get_main_queue(), ^{                │
│      // 不会在当前 RunLoop 循环执行                          │
│      // 要等到下一个循环才执行                                │
│      [self doSomething];                                    │
│  });                                                         │
│                                                              │
│  解决方案:                                                  │
│  // 方案 1:直接调用(不在 RunLoop 中)                       │
│  [self doSomething];                                        │
│                                                              │
│  // 方案 2:唤醒 RunLoop                                      │
│  CFRunLoopWakeUp(CFRunLoopGetCurrent());                      │
│                                                              │
│  // 方案 3:dispatch_sync(同步调用)                        │
│  dispatch_sync(dispatch_get_main_queue(), ^{                 │
│      [self doSomething];                                    │
│  });                                                         │
│                                                              │
│  ─────────────────────────────────────────────                │
│  场景 3:子线程维持 RunLoop 运行                              │
│  ─────────────────────────────────────────────                │
│                                                              │
│  // 创建子线程 + RunLoop                                     │
│  NSThread *thread = [[NSThread alloc]                       │
│      initWithTarget:self                                      │
│      selector:@selector(runLoopThread)                        │
│      object:nil];                                             │
│  [thread start];                                              │
│                                                              │
│  - (void)runLoopThread {                                      │
│      [[NSRunLoop currentRunLoop] addTimer:timer               │
│      forMode:NSDefaultRunLoopMode];                          │
│      [[NSRunLoop currentRunLoop] run];                        │
│  }                                                            │
│                                                              │
│  ─────────────────────────────────────────────                │
│  场景 4:GCD 的 main queue 与 RunLoop 的关系                  │
│  ─────────────────────────────────────────────                │
│                                                              │
│  • GCD 的 main queue 基于主线程的 RunLoop                      │
│  • dispatch_async(main) 的 block 通过 RunLoop 触发            │
│  • 如果主线程的 RunLoop 没有运行,block 不会执行                │
│  • 如果主线程的 RunLoop 正在等待(休眠),block 不会立即执行    │
│                                                              │
│  ─────────────────────────────────────────────                │
│  场景 5:网络请求回调在主线程执行                              │
│  ─────────────────────────────────────────────                │
│                                                              │
│  • NSURLSession 的 delegate 回调默认在主线程的 RunLoop 中执行  │
│  • 这是因为 URLSession 将回调添加到主线程的 RunLoop             │
│  • 可以通过 configuration.queue 指定回调队列                  │
│                                                              │
│  ─────────────────────────────────────────────                │
│  场景 6:performSelector 的实现原理                            │
│  ─────────────────────────────────────────────                │
│                                                              │
│  • performSelector:withObject:afterDelay:                     │
│    本质上是通过 NSTimer + RunLoop 实现                       │
│  • Timer 被添加到 RunLoop 中,在指定时间触发                   │
│  • 触发时调用目标对象的方法                                   │
│  • 如果 RunLoop 处于 UITrackingRunLoopMode,延迟会不准确        │
│                                                              │
└─────────────────────────────────────────────────────────┘
*/

4. RunLoop 源码级分析

4.1 CFRunLoop 源码关键结构

objc
/*
CFRunLoop 核心源码结构(简化版):
┌─────────────────────────────────────────────────────┐
│                                                      │
│  struct __CFRunLoop {                                │
│      CFRuntimeBase _base;                            │
│      pthread_t _pthread;                             │
│      uint32_t _modeListCount;                        │
│      CFMutableSetRef _modes;                         │  /* 模式集合           │
│      void *_dispatchQueue;                           │  /* GCD 队列          │
│      dispatch_source_t _dispatchSource;              │  /* Dispatch Source   │
│      CFStringRef _name;                              │  /* RunLoop 名称      │
│      int _spinCount;                                 │  /* 自旋计数           │
│  };                                                  │
│                                                      │
│  struct __CFRunLoopMode {                             │
│      CFRuntimeBase _base;                             │
│      CFStringRef _name;                              │  /* 模式名称           │
│      bool _stopped;                                  │
│      CFMutableSetRef _sources0;                      │  /* Source0 集合      │
│      CFMutableSetRef _sources1;                      │  /* Source1 集合      │
│      CFMutableArrayRef _observers;                   │  /* 观察者             │
│      CFMutableArrayRef _timers;                      │  /* Timer 集合         │
│      dispatch_source_t _dispatchSource;              │
│  };                                                  │
│                                                      │
│  struct __CFRunLoopObserver {                         │
│      CFRuntimeBase _base;                             │
│      uint32_t _bits;                                 │  /* 活动类型           │
│      CFMutableArrayRef _calloutArray;                │  /* callout 数组       │
│  };                                                  │
│                                                      │
│  struct __CFRunLoopSource {                           │
│      CFRuntimeBase _base;                             │
│      uint32_t _bits;                                 │
│      CFMutableSetRef _runLoops;                      │  /* 所属 RunLoop      │
│      void *_context;                                 │  /* 上下文             │
│      uint32_t _order;                                │  /* 优先级             │
│  };                                                  │
│                                                      │
└─────────────────────────────────────────────────────┘
*/

4.2 CFRunLoopRun 源码核心流程

objc
/*
CFRunLoopRun 核心循环(源码级):
┌──────────────────────────────────────────────────────┐
│                                                      │
│  int CFRunLoopRun(void) {                            │
│      int __CFRunLoopRun(CFRunLoopRef rl,            │
│          CFRunLoopModeRef rlm,                       │
│          CFTimeInterval seconds,                     │
│          Boolean stopAfterHandle) {                   │
│                                                          │
│      // 1. 通知 Observer:进入循环                       │
│      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopEntry); │
│                                                          │
│      // 2. 执行 Dispatch Source                          │
│      dispatch_resume(rlm->_dispatchSource);           │
│                                                          │
│      // 3. 处理 pending 的 Source0                        │
│      boolean sources0Handled = false;                   │
│      do {                                              │
│          sources0Handled = __CFRunLoopDoSources0(...);  │
│      } while (sources0Handled);                        │
│                                                          │
│      // 4. 处理 pending 的 Source1                        │
│      boolean sourceHandled = false;                     │
│      if (!stopAfterHandle) {                           │
│          __CFRunLoopDoSources1(...);                    │
│      }                                                  │
│                                                          │
│      // 5. 处理 Timers                                  │
│      __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());│
│                                                          │
│      // 6. 如果有 Dispatch Source,处理它                │
│      __CFRunLoopDoDispatch(...);                        │
│                                                          │
│      // 7. 进入等待(如果没有更多事件)                   │
│      if (!stopAfterHandle) {                           │
│          __CFRunLoopDoObservers(rl, rlm,               │
│              kCFRunLoopBeforeWaiting);                  │
│          __CFRunLoopWait(...);  // 等待事件              │
│          __CFRunLoopDoObservers(rl, rlm,               │
│              kCFRunLoopAfterWaiting);                   │
│      }                                                  │
│                                                          │
│      // 8. 通知 Observer:退出循环                       │
│      __CFRunLoopDoObservers(rl, rlm,                    │
│          kCFRunLoopExit);                              │
│                                                          │
│      return result;                                     │
│  }                                                      │
│                                                      │
│  ⚠️ 关键理解:                                        │
│  • RunLoop 是一个 while 循环,持续等待和处理事件         │
│  • 事件处理完成后进入等待(休眠)                        │
│  • 有事件或定时器触发时唤醒                              │
│  • 主线程的 RunLoop 永远不会退出                       │
│                                                      │
└──────────────────────────────────────────────────────┘
*/

5. RunLoop 应用场景深度分析

5.1 性能优化中的应用

objc
/*
RunLoop 在性能优化中的应用:
┌───────────────────────────────────────────────────────┐
│                                                      │
│  1. 延迟加载:                                       │
│  • 在 runLoop 的 BeforeWait 阶段进行数据加载            │
│  • 利用等待时间做后台处理                               │
│  • 在 AfterWait 阶段更新 UI                             │
│                                                      │
│  2. 批量更新:                                       │
│  • 在 BeforeTimers 阶段合并多个更新                    │
│  • 减少重复的 layout/relayout                          │
│                                                      │
│  3. 避免频繁更新:                                   │
│  • 不要在 BeforeSources 阶段做耗时操作                 │
│  • 使用 CADisplayLink 替代 Timer(与屏幕刷新同步)     │
│                                                      │
│  4. 主线程保活:                                    │
│  • 子线程使用 RunLoop 保持活跃                        │
│  • 添加 Timer/Source 到 RunLoop                      │
│  • 调用 [[NSRunLoop currentRunLoop] run]              │
│                                                      │
│  5. 网络请求回调:                                   │
│  • URLSession 默认在主线程的 RunLoop 中回调            │
│  • 可以通过 configuration.queue 指定其他队列            │
│                                                      │
└───────────────────────────────────────────────────────┘

RunLoop 的常见坑:
┌───────────────────────────────────────────────────────┐
│  坑 1:Timer 在 UI 滚动时被暂停                       │
│  解:添加到 common modes                              │
│                                                      │
│  坑 2:dispatch_async(main) 不立即执行                │
│  解:直接调用或 CFRunLoopWakeUp                       │
│                                                      │
│  坑 3:performSelector:afterDelay: 不准                │
│  解:使用 CADisplayLink 或 NSTimer                    │
│                                                      │
│  坑 4:子线程 RunLoop 自动退出                         │
│  解:添加 Timer/Source 或调用 run                     │
│                                                      │
│  坑 5:RunLoop 阻塞导致事件不处理                      │
│  解:不要在 RunLoop 中做耗时操作                       │
│                                                      │
└───────────────────────────────────────────────────────┘
*/

6. 面试题汇总

高频面试题

Q1: RunLoop 的核心作用是什么?

  • RunLoop 是事件处理和管理线程运行的核心机制
  • 保持线程活跃(主线程通过 RunLoop 保持运行)
  • 调度事件和定时器(Source0/Source1/Timer)
  • 协调事件分发(UI 事件、网络回调、定时器)

Q2: RunLoop 的 Mode 有哪些?

  • NSDefaultRunLoopMode:默认模式(普通事件处理)
  • UITrackingRunLoopMode:UI 滚动模式(保证流畅)
  • NSRunLoopCommonModes:伪模式(归组)
  • UIDefaultRunLoopMode、NSURLSession 模式等

Q3: RunLoop 的 Source 和 Timer 的关系?

  • Source0:非系统事件,需要手动触发
  • Source1:系统事件/端口,通过 mach_port 触发
  • Timer 是 Source 的一种封装,底层也是 mach port
  • Timer 在指定的 timeInterval 后触发,实际受 Mode 影响
  • 在 UITrackingRunLoopMode 下 Timer 不会触发
  • 解决方案:添加到 common modes 或使用 CADisplayLink

Q4: dispatch_async(main) 为什么不立即执行?

  • dispatch_async 通过 RunLoop 的 Source1 触发
  • RunLoop 当前循环已经处理完 Source1,要等到下一个循环
  • 解决方案:直接调用、CFRunLoopWakeUp、或 dispatch_sync

Q5: RunLoop 的 Observer 有哪些活动类型?

  1. Entry:进入循环
  2. BeforeTimers:定时器前
  3. BeforeSources:源前
  4. BeforeWait:等待前
  5. AfterWait:等待后
  6. Exit:退出循环

Q6: 如何实现一个子线程的长期运行?

  • 创建子线程
  • 在子线程中创建 RunLoop
  • 添加 Timer/Source 到 RunLoop
  • 调用 [[NSRunLoop currentRunLoop] run]
  • 或者使用 dispatch queue + RunLoop 配合

7. 参考资源