贰:RunLoop的内部结构
RunLoopMode
iOSRunLoop一共有几个核心的类,分别是:
- CFRunLoop
- CFRunLoopMode
- CFRunLoopSource
- CFRunLoopObserver
- CFRunLoopTimer
RunLoopMode定义
以下是源码中的关于RunLoop以及RunLoopMode的部分定义:
```c struct CFRunLoop { ... pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; ... }
struct CFRunLoopMode { ... CFMutableSetRef _source0; CFMutableSetRef _source1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; ... } ```
根据上面的RunLoop以及RunLoopMode的定义,我们可以得出以下的对应关系:一个RunLoop可以包含有多个Mode,一个mode可以包含多个Source0 & Source1 & Observer & Timer。
每次调用RunLoop的主函数的时候,只能指定其中一个Mode,这个Mode就被称问Common Mode。如果需要切换Mode, 只能退出RunLoop重新指定Mode进入。这样做是为了分割开不同组别的Source/Timer/Observer, 让其互不影响。RunLoop一次只在一个Mode下运行。
CFRunLoopSourceRef
Source是RunLoop的数据源(input source)的一个抽象类,Source有两个版本:Source 0和Source 1。
```c struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; pthread_mutex_t _lock; CFIndex _order; / immutable / CFMutableBagRef _runLoops;
// 共用体
// 同一时刻,共用体只存放了一个被选择的成员
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
}; ```
通过version的不同来区分两种不同类型的Source,而这两种source也有着很明显的区别。
Source0
Souece0包含了一个回调(函数指针),它并不能直接唤醒RunLoop,只能去主动去调用方法来唤醒RunLoop。如果我们自己创建来一个Source0,想通过Source0来唤醒RunLoop,那么需要两个步骤:
- 发送信号:
CFRunLoopSourceSignal(rs)
- 手动唤醒:
CFRunLoopWakeUp(RunLoop.main.getCFRunLoop())
也就是说我们需要去调用CFRunLoopSourceSignal(source), 来标记这个Source正在等待处理,然后需要手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让它来处理这个事件。
Source1
它被RunLoop和kernel管理,通过mach_port来驱动(特指基于port的事件),比如CFMachPort, CFMessagePort,NSSocketPort。Mach Port是很轻量级的方式用于进程之间的通信,它可以被理解为一个通信的channel。比如点触事件,其实就是由BackService直接将IOEvent传递给处理该事件的前台进程。这就涉及到了进程间的通信,使用的就是Source1。
CFRunLoopTimerRef
基于时间的触发器,它和NSTimer是toll-free bridged的,可以混用(它的底层是基于mk_timer的)。它是会被RunLoop Mode所影响的(而GCD的timer是不会被RunLoopMode影响的)。当它被加入RunLoop之后,RunLoop将会注册与之对应的时间点,当时间点到达的时候,Runloop将被唤醒来执行回调,如果这个线程被阻塞了或者此时RunLoop不在这个Mode中,那么这个触发的时间点点并不会执行回调,将等到下一次循环的时间点被触发。
CFRunLoopObserverRef
它是观察者,每个Observer都包含了对应一个回调(函数指针),一个Mode中可以有多个观察者,当RunLoop的状态发生改变时,观察者就可以通过回调接受这个变化。可以观测的时间点有以下几个:
c
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
};
这些source & timer & observer都统一被称为mode item,一个item可以被同时加入多个mode。但是一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,那么RunLoop就会直接退出,不进入循环。
Common Modes
同时要注意有一个概念叫做Common Mode。一个Mode可以把自己标记为Common(将mode添加到CommonMode中)。
swift
// 当然Mode中需要加observer/Timer/source
let mode = CFRunLoopMode.init(rawValue: "fff" as CFString)
CFRunLoopAddCommonMode(RunLoop.main.getCFRunLoop(), mode)
这样的话,每当RunLoop的内容变化时(切换Mode运行时),RunLoop会自动将commonModeItems中的Source & Observer & Timer同步到具有"Common"标记的所有Mode中。
举个例子:主线程的RunLoop中有两个预先设置的Mode,其一是kCFRunLoopDefaultMode, 其二是UITrackingRunLoopMode。这两个Mode都被标记为Common属性。DefaultMode是默认状态,TrackingMode是追踪ScrollView滑动时的状态。当创建一个Timer时,Timer会得到重复的回调。但是当滑动一个UIScrollView时,RunLoop会将Mode切换都TrackingMode,这个时候Timer的回调就不会生效了。
所以需要这个Timer在两个Mode中都可以得到回调,那么其中一种方式就是将这个Timer分别放入到这两个Mode中。还有一种方式,就是将Timer放到顶层的RunLoop的CommonModeItems中,这其中的items都会被自动更新到所有具备Common属性的Mode中。
Mode的分类
从Apple的官方文档中,我们可知其实系统提供了几个Mode:
- kCFRunLoopDefaultMode: App的默认Mode,通常主线程是在这个Mode下运行的。
- UITrackingRunLoopMode:界面追踪Mode,用于ScrollView的追踪,保证界面滑动不受影响
- UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不再使用
- GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
- KCFRunLoopCommonModes:占位的Mode
Mode实例
直接将当前线程的RunLoop在控制台输出,可以得到如下的结构:
```c CFRunLoop { current mode = KCFRunLoopDefaultMode, common modes = { UITrackingRunLoopMode, KCFRunLoopDefaultMode }
common mode items = {
//source 0
CFRunLoopSource = { order = -1,{
callout = PurpleEventSignalCallback}} //GraphicsServices
CFRunLoopSource = { order = 0,{
callout = HandleDelegateSource(void*)}} //WebCore
CFRunLoopSource = { order = 0, {
callout = __NSThreadPerformPerform}} //Foundation
CFRunLoopSource = { order = 0, {
callout = FBSSerialQueueRunLoopSourceHandler}} // FrontBoardServices
CFRunLoopSource = { order = -1,{
callout = __eventQueueSourceCallback}} //UIKitCore
CFRunLoopSource = { order = 0,{
callout = WTF::RunLoop::performWork(void*)}} //JavaScriptCore
CFRunLoopSource = { order = -2,{
callout = __eventFetcherSourceCallback}} //UIKitCore
// source 1(mach port)
CFRunLoopSource {order = 0, {port = 17923}}
CFRunLoopSource {order = 0, {port = 12039}}
CFRunLoopSource {order = 0, {port = 16647}}
CFRunLoopSource {order =-1, {
callout = PurpleEventCallback}}
CFRunLoopSource {order = 0, {port = 2407,
callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
CFRunLoopSource {order = 0, {port = 1c03,
callout = __IOHIDEventSystemClientAvailabilityCallback}}
CFRunLoopSource {order = 0, {port = 1b03,
callout = __IOHIDEventSystemClientQueueCallback}}
CFRunLoopSource {order = 1, {port = 1903,
callout = __IOMIGMachPortPortCallback}}
// observer
CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
callout = _wrapRunLoopWithAutoreleasePoolHandler}
CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting
callout = _UIGestureRecognizerUpdateObserver}
CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit
callout = _beforeCACommitHandler}// UIKitCore
CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit
callout = _afterCACommitHandler} // UIKitCore
CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit
callout = CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*)}
CFRunLoopObserver {order = -2147483648, activities = 0x46,// BeforeTimer | BeforeSource | AfterWait
callout = __trackRunLoopTimes} // UIKitCore
CFRunLoopObserver {order = 2147483647, activities = 0x20, // BeforeWaiting
callout = __trackRunLoopTimes} // UIKitCore
CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
callout = _wrapRunLoopWithAutoreleasePoolHandler}
// timer
CFRunLoopTimer {valid = Yes, firing = No, interval = 3.1536e+09, tolerance = 0,
next fire date = 1.17402502e+09 (504911209 @ 12130246606742925),
callout = CA::timer_callback(__CFRunLoopTimer*, void*)} // QuartzCore
CFRunLoopTimer {valid = Yes, firing = No, interval = 3, tolerance = 0,
next fire date = 669113787 (-20.825485 @ 12377085760065),
callout = [FSPagerView.FSPagerView flipNextWithSender:]}
}
modes = {
CFRunLoopMode { //kCFRunLoopDefaultMode
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode { //UITrackingRunLoopMode
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode { // kCFRunLoopCommonModes
source0 = null,
source1 = null,
timers = null,
observers = null,
},
CFRunLoopMode { // GSEventReceiveRunLoopMode
source0 = {
CFRunLoopSource {order = 1,
callout = PurpleEventSignalCallback}
},
source1 = {},
observers = null,
timers = null,
}
}
}
} ```
我们可以很明确的看到,一个RunLoop下对应有很多种不同的Mode,而每个Mode中都有对应的source,observer等元素。当然要想看到更多的Mode类型的话,可以到Apple关于RunLoop的官方描述中查看。
RunLoop的结构
接下来我直接PO一段RunLoop的源码:
```c // GitHub上开源的CoreFoundation,run方法其实还是调用了运行defaultMode的方法 extension RunLoop { public func run() { while run(mode: .default, before: Date.distantFuture) { } } ... }
// 使用指定的模式来启动RunLoop
int CFRunInMode(modeName, seconds, returnAftersourceHandled) {
return CFRunloopRunSpecific(CFRunLoopGetCurrent(),
modeName,
seconds,
returnAftersourceHandled);
}
int CFRunloopRunSpecific(runloop, modeName, seconds, returnAftersourceHandled) { // 根据mode名找到对应的mode CFRunLoopModeRef currentMode = __CFRunLoopMode(r1, modeName, false);
if (Null == currentMode || CFRunLoopModeIsEmpty(currentMode)) {
return;
}
int result = KCFRunLoopRunFinished;
// 1、通知observers:RunLoop即将进入RunLoop (KCFRunLoopEntry)
CFRunLoopDoObservers(rl, currentMode, KCFRunLoopEntry);
result = __CFRunLoopRun(runloop, currentMode, ...);
// 内部函数
int __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled) {
bool didDispatchPortLastTime = true;
in retVal = 0;
do {
// runloopMode中的所有port
__CFPortSet waitSet = rlm->_portSet;
// 2、通知observers:RunLoop即将准备处理timer
CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 3、通知observers:RunLoop即将准备处理source(不基于port的即source0)
CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// -- 执行被加入的block
CFRunLoopDoBlocks(rl, rlm);
// 4、处理Source0:Runloop处理source(不基于port的source)
bool sourceHandledThisLoop = CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// -- 执行被加入的block
if (sourceHandledThisLoop) {
CFRunLoopDoBlocks(rl, rlm);
}
// 5、如果有Source1处于Ready状态:RunLoop跳到第九步处理Source(基于port的source)
// 这里指定了接收消息的端口是dispatchPort:如Dispatch.main.async{}
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
bool hasMsg = CFRunLoopServiceMachPort(dispatchPort, msg, &livePort, 0);
if (hasMsg) goto handle_msg;
}
didDispatchPortLastTime = false;
// 6、通知Observers:RunLoop即将进入休眠(kCFRunLoopBeforeWaiting)
CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 7、线程进入睡眠:调用CFRunLoopServiceMachPort等待被唤醒,被以下几个唤醒
// * 一个基于port的source事件(·)
// * 一个到点要执行的timer(Timer)
// * RunLoop要超时了(RunLoopTimeOut)
// * RunLoop被显式的唤醒了
CFRunLoopServiceMachPort(waitSet, msg, livePort, TIMEOUT_INFINITY);
// 8、通知Observers:线程被唤醒了
CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 9、收到消息,处理消息。
handle_msg;
// * 无port啥也不做
if (Mach_Port_Null == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// * 标记为唤醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// * 标记为Timer唤醒:Timer/NSTimer唤醒
} else if (livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!_CFRunLoopDoTimers(rl, rlm, mach_absolute_timer())) {
CFArmNextTimerInMode(rlm, rl);
}
// * 标记为GCD唤醒:由main_queue派发的block
} else if (livPort === dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
// * 标记为Source1唤醒
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 执行加入到runloop中的block
CFRunLoopDoBlocks(rl, rlm);
// 判断runloop是否结束
// * 刚刚处理完事件
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
// * runloop超时了
} else if (timeout) {
retVal = kCFRunLoopRunTimedOut;
// * 被外部调用者强制停止了
} else if (CFRunLoopIsStopped(rl)) {
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
// * runloop的Mode中source/timer/observer一个都没有了
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retValue);
}
//10、通知Observers:RunLoop即将离开
CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
} ```
它的内部逻辑是一个while循环,可以一直使线程处于存活的状态,有事情来就处理事情,没有事情来,就处于休息状态。基于上方的流程,我也创建了一个RunLoop流程图。
根据上述的流程,我们可以梳理出相应的回调:
```c { /// 1. 通知Observers,即将进入RunLoop /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush(); CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION(kCFRunLoopEntry); do {
/// 2. 通知 Observers: 即将触发 Timer 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 触发 Source0 (非基于port的) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即将进入休眠
/// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. 通知Observers,线程被唤醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. 如果是被Timer唤醒的,回调Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
/// 10. 通知Observers,即将退出RunLoop
/// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
} ```
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。