iOS底层(七)_RunLoop_基础
RunLoop
基本作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时事件等)
- 节省CPU资源、提高程序性能:该做事时做事,该休息时休息
iOS中有2套API来访问和使用RunLoop
Foundation :NSRunLoop
CoreFoundation :CFRunLoopRef
NSRunLoop 和 CFRunLoopRef 都代表着RunLoop对象
NSRunLoop 是基于 CFRunLoopRef 的一层OC包装
/// 获取当前runloop对象
NSRunLoop *runloop1 = [NSRunLoop currentRunLoop];
CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
RunLoop 与 线程
每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里
,线程最为key
,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
RunLoop 常见的2中Mode
kCFRunLoopDefaultMode
(NSDefaultRunLoopMode) : App 的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode
: 界面跟踪Mode, 用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
-
CFRunLoopModeRef
代表RunLoop的运行模式
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
- RunLoop启动时
只能选择其中一个Model
,作为currentMode - 如需
切换Mode,只能是退出当前Loop
,再重新选择一个Mode进入
(切换Mode不会导致程序退出) - 不同组的Source0/Source1/Timer/Observer 能分割开来,互不影响
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
CFRunLoopSourceRef
是事件产生的地方,Source有两个版本:Source0 和 Source1。
Source0
: 只包含一个回调(函数指针),它并不能主动触发事件
。使用时,你需要先调用CFRunLoopSourceSignal(source)
,将这个Source标记为待处理
,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop
,让其处理这个事件Source1
: 包含一个mach_port和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这中Source能主动唤醒RunLoop的线程
CFRunLoopTimeRef
是基于时间的触发器,它和NSTimer是toll-free bridged的,可以混用。其包含一个时间长度
和一个回调
(函数指针)。当其加入到RunLoop时,RunLoop会注册对应的时候点
,当时间点到时,RunLoop会被唤醒以执行那个回调
。
CFRunLoopObserverRef 是观察者
,每个Observer都包含了一个回调(函数指针),当RunLoop的状态发生变化
时,观察者就能通过回调接受到这个变化
。可以观测的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
以上写的CFRunLoopSourceRef、CFRunLoopTimeRef、CFRunLoopObserverRef被统称为mode item
,一个mode item 可以同时加入多个model
。但一个model item被重复加入同一个mode时是不会有效果的
。如果一个mode中一个item都没有,则RunLoop会直接退出,不进入循环。
RunLoop核心源码
/// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// • 一个基于 port 的Source 的事件。
/// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,处理消息。
handle_msg:
/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
参考文章:
源码解析之RunLoop详解
关于RunLoop部分源码的注释
深入理解RunLoop
iOS中RunLooop机制浅探
iOS保持界面流畅的技巧
微信iOS卡顿监控系统