RunLoop 深度解析
目录
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);
}
运行流程:
- 通知 Observer:即将进入 RunLoop
- 通知 Observer:即将处理 Timer
- 通知 Observer:即将处理 Source0
- 处理 Source0
- 如果有 Source1 就绪,立即处理
- 通知 Observer:线程即将休眠
- 休眠,等待被唤醒
- 端口有消息(Source1)
- Timer 到时间
- RunLoop 超时
- 被手动唤醒
- 通知 Observer:线程被唤醒
- 处理唤醒时收到的事件
- 如果是 Timer,处理 Timer 回调
- 如果是 dispatch 到 main queue 的 block,执行 block
- 如果是 Source1,处理 Source1
- 根据结果决定是否继续循环
- 如果处理了事件,回到步骤 2
- 如果超时,回到步骤 2
- 如果被停止,退出循环
2.2 Mode 切换机制
RunLoop 在同一时间只能运行在一个 Mode 下,切换 Mode 需要退出当前循环,重新以新 Mode 进入。
Mode 切换的触发时机:
- ScrollView 开始滑动:切换到
UITrackingRunLoopMode - ScrollView 停止滑动:切换回
NSDefaultRunLoopMode - 系统事件:可能切换到其他 Mode
为什么需要 Mode 切换?
- 隔离不同场景的事件处理
- 避免滑动时 Timer 影响滚动性能
- 提供更精细的事件控制
2.3 Source 的分类和处理
Source0(非基于 Port)
特点:
- 需要手动标记为待处理状态
- 不能主动唤醒 RunLoop
- 处理 App 内部事件
处理流程:
- 事件发生(如触摸事件)
- 系统标记 Source0 为待处理
- RunLoop 被其他方式唤醒(如 Source1)
- RunLoop 检查并处理所有待处理的 Source0
- 调用 Source0 的回调函数
Source1(基于 Port)
特点:
- 基于 mach_port(进程间通信端口)
- 可以主动唤醒 RunLoop
- 处理系统级事件
处理流程:
- 系统事件发生(如端口收到消息)
- 内核通过 mach_port 发送消息
- Source1 接收到消息,唤醒 RunLoop
- RunLoop 处理 Source1 事件
- 调用 Source1 的回调函数
2.4 Timer 的实现原理
Timer 在底层是基于 mach_absolute_time() 实现的,精度很高。
Timer 的工作机制:
- 创建 Timer:设置时间间隔和回调
- 添加到 RunLoop:Timer 被添加到当前 Mode 的 Timer 列表
- RunLoop 检查:每次循环检查 Timer 是否到期
- 触发回调:如果到期,调用 Timer 的回调方法
- 重复执行:如果是重复 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() 的休眠。
休眠原理:
- RunLoop 调用
mach_msg()等待消息 - 如果没有消息,线程进入内核态休眠
- 有消息时,内核唤醒线程
- 线程回到用户态,继续执行
优势:
- 真正的休眠,不占用 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,而是一个占位符。
实现原理:
NSRunLoopCommonModes是一个字符串数组,包含多个 Mode 名称- 当向 Common Modes 添加 item 时,实际上会添加到所有 common modes 中
- 当切换 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 的工作流程:
触摸事件处理
- 用户触摸屏幕 → Source1 接收事件 → 唤醒 RunLoop
- RunLoop 处理触摸事件 → 调用 UIResponder 的方法
- 事件沿着响应链传递
UI 更新
- 修改 UI 属性(如 frame、backgroundColor)不会立即生效
- 修改会在当前 RunLoop 循环结束时统一渲染
- 通过
setNeedsDisplay标记需要重绘的视图
布局更新
setNeedsLayout标记需要重新布局- 在 RunLoop 的
kCFRunLoopBeforeWaiting时执行布局计算 - 然后执行绘制操作
代码示例:
// 修改视图属性
self.view.backgroundColor = [UIColor redColor];
// 此时视图不会立即更新,而是等到 RunLoop 休眠前统一更新
// 强制立即更新(不推荐,会阻塞主线程)
[self.view layoutIfNeeded];
4.2 子线程中创建 RunLoop
默认情况下,子线程没有 RunLoop,线程执行完任务后就会退出。如果需要让子线程保持存活并处理任务,需要手动创建并运行 RunLoop。
创建常驻线程的步骤:
- 创建线程
- 在线程中获取 RunLoop(会自动创建)
- 添加 Source 或 Timer 到 RunLoop(否则 RunLoop 会立即退出)
- 运行 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 才能正常工作,这是很多开发者容易忽略的点。
常见问题:
Timer 不触发
- 原因:没有添加到 RunLoop 或添加到了错误的 Mode
- 解决:确保添加到正确的 RunLoop 和 Mode
滑动时 Timer 暂停
- 原因:Timer 添加到了
NSDefaultRunLoopMode,滑动时切换到UITrackingRunLoopMode - 解决:添加到
NSRunLoopCommonModes
- 原因:Timer 添加到了
代码示例:
// 方式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 密切相关:
- 事件产生:用户触摸屏幕
- 系统捕获:IOKit 捕获触摸事件
- 事件传递:通过 Source1 传递给主线程 RunLoop
- RunLoop 唤醒:RunLoop 被唤醒
- 事件分发:RunLoop 处理 Source1,调用 UIApplication 的
sendEvent: - 响应链传递:事件沿着响应链传递(UIApplication → UIWindow → ViewController → View)
- 处理事件:找到合适的响应者处理事件
代码示例:
// 重写 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 的状态
- 记录
kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting的时间间隔 - 如果间隔过长,说明主线程被阻塞
实现代码:
@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 性能优化建议
避免在主线程 RunLoop 中执行耗时操作
- 耗时操作会阻塞主线程,导致 UI 卡顿
- 使用后台线程处理耗时任务
合理使用 CommonModes
- 只有需要在滑动时也执行的任务才添加到 CommonModes
- 避免添加过多任务到 CommonModes,影响滑动性能
及时移除不需要的 Observer、Timer、Source
- 避免内存泄漏
- 减少 RunLoop 的负担
使用 GCD 替代 RunLoop(如果可能)
- GCD 更简单,性能更好
- 不依赖 RunLoop,更可靠
6.4 调试技巧
- 打印 RunLoop 信息
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSLog(@"Current RunLoop: %@", runLoop);
NSLog(@"Current Mode: %@", CFRunLoopCopyCurrentMode(runLoop));
- 监控 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);
- 检查 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];
浙公网安备 33010602011771号