注:本篇博客和 ibireme 的 深入理解RunLoop 基本一样,稍微根据自己的实际学习进行了一些修改。
学习过程中用的是swift-corelibs-foundation版本的RunLoop进行阅读学习。
能力有限,如果有理解的不对的地方,还希望能帮忙指出。
一般来说,一个线程一次只能执行一个任务,执行完毕之后线程就会退出。例如一个hello world
程序,在执行完毕之后就会退出。
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("hello world");
}
//输出
hello world
//线程退出
那我们的App是怎么做到让我们的主线程不退出,且可以随时处理我们的事件呢?这里就我们就需要用到Event Loop。通常它会像这样:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
Event Loop 在很多系统和框架里都有实现,macOS/iOS中使用的是RunLoop。
RunLoop实际是一个对象,负责处理接收到的事件和消息,并提供了一个入口函数来执行上面的Event Loop逻辑。线程执行了这个函数之后,这个函数内部就会处于“接收消息→等待→处理”的循环中,直到循环结束(比如传入quit的消息),函数返回。
macOS/iOS系统中提供了两个对象:NSRunLoop和CFRunLoopRef。
- CFRunLoopRef在CoreFoundation框架中,提供了纯C函数的API,并且所有API都是线程安全的。
- NSRunLoop则是基于CFRunLoopRef的封装,提供面向对象的API,但是这些API是非线程安全的。
CFRunLoopRef的代码开源可以在https://opensource.apple.com/tarballs/CF/进行下载源码查看。
Swift开源后,苹果又维护了一个跨平台的CoreFoundatio 版本:https://github.com/apple/swift-corelibs-foundation/。直接下载release版本的代码,可以直接编译通过,然后你就可以单步调试玩了。好评~
注意:下面的代码都是swift-corelibs-foundation工程里面的代码。
1. RunLoop与线程的关系
首先,iOS 开发中能遇到两个线程对象:pthread_t
和NSThread
。过去苹果有份文档指明了NSThread
只是pthread_t
的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的mach thread
。
虽然,苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是pthread_t
和NSThread
是一一对应的。比如,可以通过pthread_main_thread_np()
或[NSThread mainThread]
来获取主线程;也可以通过pthread_self()
或[NSThread currentThread]
来获取当前线程。此外,CFRunLoop
是基于pthread
来管理的。
苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:
CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);
这两个函数内部的逻辑大概是下面这样:
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
/// 访问__CFRunLoops时的锁
static CFLock_t loopsLock = CFLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
// 如果为空则t设置为主线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
// 访问__CFRunLoops字典时加锁
__CFLock(&loopsLock);
// 没有__CFRunLoops字典时创建一个
if (!__CFRunLoops) {
// 初始化全局的__CFRunLoops字典,并先为主线程创建一个RunLoop
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
}
// 通过线程取出相应的RunLoop
CFRunLoopRef newLoop = NULL;
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 取不到时创建一个,并存入字典
newLoop = __CFRunLoopCreate(t);
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFUnlock(&loopsLock);
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
if (newLoop) { CFRelease(newLoop); }
// 如果传入线程和当前线程相同
if (pthread_equal(t, pthread_self())) {
// 注册一个回调,当线程销毁是,顺便也销毁对应的RunLoop
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
#else
_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
}
}
return loop;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
从上面的代码可以看出:
- 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。
- 线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。
- RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。我们只能在一个线程的内部获取其 RunLoop(主线程除外)。
2. RunLoop对外的接口
在 CoreFoundation 里面关于 RunLoop 有5个类:
typedef CFStringRef CFRunLoopMode CF_EXTENSIBLE_STRING_ENUM;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
- CFRunLoopMode
CFRunLoopModeRef
类并没有对外暴露,只是通过CFRunLoopRef
接口进行了封装。他们的关系如下:
一个RunLoop
包含若干个Mode
,每个Mode
又包含若干个Source/Timer/Observer
。每次调用RunLoop
的主函数时,只能指定其中一个Mode
,这个Mode
被称作CurrentMode
。如果需要切换Mode
,只能退出Loop
,再重新指定一个Mode
进入。这样做主要是为了分隔开不同组的Source/Timer/Observer
,让其互不影响。
-
CFRunLoopSourceRef
CFRunLoopSourceRef是事件产生的地方。
Source
有两个版本:Source0
和Source1
。-
Source0只包含了一个回调(函数指针),它并不能主动触发事件。
使用时,需要先调用
CFRunLoopSourceSignal(source)
,将这个Source
标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)
来唤醒RunLoop
,让其处理这个事件。 -
Source1包含了一个
mach_port
和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种
Source
能主动唤醒RunLoop
的线程,其原理在下面会讲到。
-
-
CFRunLoopTimerRef
CFRunLoopTimerRef 是基于时间的触发器,它和
NSTimer
是toll-free bridged
的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到
RunLoop
时,RunLoop
会注册对应的时间点,当时间点到时,RunLoop
会被唤醒以执行那个回调。 -
CFRunLoopObserverRef
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 kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有事件 };
上面的Source/Timer/Observer
被统称为 mode item,一个item
可以被同时加入多个mode
。但一item
被重复加入同一个mode
时是不会有效果的。如果一个mode
中一个item
都没有,则RunLoop
会直接退出,不进入循环。
3. RunLoop的Mode
CFRunLoopMode
和CFRunLoop
的结构大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
这里有个概念叫CommonModes:一个Mode
可以将自己标记为Common
属性(通过将其ModeName
添加到RunLoop
的commonModes
中)。每当RunLoop
的内容发生变化时,RunLoop
都会自动将 _commonModeItems
里的Source/Observer/Timer
同步到具有Common
标记的所有Mode
里。
应用场景举例:
主线程的RunLoop
里有两个预置的Mode
:
kCFRunLoopDefaultMode
UITrackingRunLoopMode
这两个Mode
都已经被标记为Common
属性。DefaultMode
是App平时所处的状态,TrackingRunLoopMode
是追踪ScrollView滑动时的状态。当你创建一个Timer
并加到DefaultMode
时,Timer
会得到重复回调,但此时滑动一个TableView时,RunLoop
会将mode
切换为TrackingRunLoopMode
,这时Timer
就不会被回调,并且也不会影响到滑动操作。
有时你需要一个Timer
,在两个Mode
中都能得到回调,一种办法就是将这个Timer
分别加入这两个Mode
。还有一种方式,就是将Timer
加入到顶层的RunLoop
的commonModeItems
中。commonModeItems
被RunLoop
自动更新到所有具有Common
属性的Mode
里去。
CFRunLoop
对外暴露的管理Mode
接口只有下面2个:
CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);
CF_EXPORT CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
Mode
暴露的管理mode item
的接口有下面几个:
CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
可以注意到mode
并不像Source/Timer/Observer
一样有 Remove 方法,所以mode
只能增加,不能减少。
当你传入一个新的mode name
但RunLoop
内部没有对应mode
时,RunLoop
会自动帮你创建对应的 CFRunLoopModeRef
。
苹果公开提供的Mode
有两个:kCFRunLoopDefaultMode
(NSDefaultRunLoopMode
) 和 UITrackingRunLoopMode
,你可以用这两个Mode Name
来操作其对应的Mode
。
同时苹果还提供了一个操作Common
标记的字符串:kCFRunLoopCommonModes
(NSRunLoopCommonModes
),你可以用这个字符串来操作Common Items
,或标记一个Mode
为Common
。使用时注意区分这个字符串和其他mode name
。
4. RunLoop的内部逻辑
根据苹果在文档里的说明,RunLoop 内部的逻辑大致如下:
RunLoop
的启动:
/// 用DefaultMode启动
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
/// 用指定的Mode启动,并允许设置RunLoop的超时时间
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
我们可以看到最后都是调用了CFRunLoopRunSpecific
:
/// RunLoop的实现
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
// 安全监测,kCFRunLoopCommonModes可以理解为一个占位符,并不是一个特定的Mode
// 在启动时NULL和kCFRunLoopCommonModes是不能使用的
if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName);
_CFRunLoopError_RunCalledWithInvalidMode();
});
return kCFRunLoopRunFinished;
}
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根据modeName找到对应的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 如果没有找到,或者source/timer/observer为空,则直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
// 一些初始化配置
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
// 1.通知Observers:RunLoop即将进入loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_START, rl, currentMode, seconds, previousMode);
// 内部函数,进入loop循环
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_END, rl, currentMode, seconds, previousMode);
// 10.通知Observers:RunLoop即将退出loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
我们再来看一下内部函数中loop部分的实现。代码可能比较多,虽然我已经尽量删了很多条件编译了...
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
// 判断RunLoop和RunLoopMode是否停止,提前返回
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
// 这部分可以不看
#if __HAS_DISPATCH__
__CFPort dispatchPort = CFPORT_NULL;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#endif
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
#if __HAS_DISPATCH__
dispatch_source_t timeout_timer = NULL;
#endif
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
#if __HAS_DISPATCH__
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
#endif
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
#if __HAS_DISPATCH__
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
#endif
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
// 下面开始,上面一堆大概意思就是用dispatch来管理
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
uint8_t msg_buffer[3 * 1024];
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
__CFPortSet waitSet = rlm->_portSet;
__CFRunLoopUnsetIgnoreWakeUps(rl);
// 2. 通知Observers:RunLoop即将触发Timer
if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
}
// 3. 通知Observers:RunLoop即将触发Source0(非port)回调
if (rlm->_observerMask & kCFRunLoopBeforeSources) {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
}
// 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// 4. RunLoop触发Source0(非port)回调
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 执行被加入的block
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 5. 如果有Source1(基于port)处于ready状态,直接处理这个Source1然后跳转去处理消息
#if __HAS_DISPATCH__
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {
goto handle_msg;
}
}
#endif
didDispatchPortLastTime = false;
// 6. 通知Observers: RunLoop的线程即将进入休眠。
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
#if __HAS_DISPATCH__
__CFPortSetInsert(dispatchPort, waitSet);
#endif
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
msg = (mach_msg_header_t *)msg_buffer;
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// • 一个基于port的Source的事件
/// • 一个Timer到时间了
/// • RunLoop自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
#if __HAS_DISPATCH__
__CFPortSetRemove(dispatchPort, waitSet);
#endif
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 9. 收到消息,处理消息。
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_NOTHING, rl, rlm, livePort, 0);
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_WAKEUP, rl, rlm, livePort, 0);
// do nothing on Mac OS
}
// 9.1 如果一个Timer到时间了,触发这个Timer的回调
// __CFRunLoopDoTimers
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_TIMER, rl, rlm, livePort, 0);
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
/* --- DISPATCHES --- */
// 9.2 如果有dispatch到main_queue的block,执行block
// __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
#if __HAS_DISPATCH__
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_DISPATCH, rl, rlm, livePort, 0);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_DISPATCH | DBG_FUNC_START, rl, rlm, msg, livePort);
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_DISPATCH | DBG_FUNC_END, rl, rlm, msg, livePort);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
}
#endif
/* --- SOURCE1S --- */
// 9.3 如果一个Source1(基于port)发出事件了,处理这个事件
else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_SOURCE, rl, rlm, 0, 0);
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
} else {
os_log_error(_CFOSLog(), "__CFRunLoopModeFindSourceForMachPort returned NULL for mode '%@' livePort: %u", rlm->_name, livePort);
}
}
/* --- BLOCKS --- */
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
// 执行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
//确定返回值
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入loop时参数说处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (0 == retVal);
// 释放
#if __HAS_DISPATCH__
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else
#endif
{
free(timeout_context);
}
return retVal;
}
可以看到,实际上RunLoop
就是这样一个函数,其内部是一个do-while循环。当你调用CFRunLoopRun()
时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。
5. RunLoop的底层实现
从上面代码可以看到,RunLoop
的核心是基于mach port
的,其进入休眠时调用的函数是mach_msg()
。
为了解释这个逻辑,下面稍微介绍一下 SX/iOS
的系统架构。
苹果官方将整个系统大致划分为上述4个层次:
- 应用层包括用户能接触到的图形应用,例如Spotlight、Aqua、SpringBoard等。
- 应用框架层即开发人员接触到的Cocoa等框架。
- 核心框架层包括各种核心框架、OpenGL等内容。
- Darwin即操作系统的核心,包括系统内核、驱动、Shell 等内容,这一层是开源的,其所有源码都可以在 opensource.apple.com 里找到。
我们在深入看一下Darwin
这个核心的架构:
其中,在硬件层上面的三个组成部分:Mach、BSD、IOKit(还包括一些上面没标注的内容),共同组成了XNU内核。
- XNU内核的内环被称作Mach,其作为一个微内核,仅提供了诸如处理器调度、IPC (进程间通信)等非常少量的基础服务。
- BSD层可以看作围绕Mach层的一个外环,其提供了诸如进程管理、文件系统和网络等功能。
- IOKit层是为设备驱动提供了一个面向对象(C++)的一个框架。
Mach本身提供的 API 非常有限,而且苹果也不鼓励使用Mach 的 API,但是这些API非常基础,如果没有这些API的话,其他任何工作都无法实施。在Mach中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为”对象”。和其他架构不同, Mach的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信。”消息”是Mach中最基础的概念,消息在两个端口 (port) 之间传递,这就是Mach的IPC(进程间通信)的核心。
Mach消息
Mach的消息定义是在 <mach/message.h> 头文件的,很简单:
typedef struct{
mach_msg_header_t header;
mach_msg_body_t body;
} mach_msg_base_t;
typedef struct{
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_port_name_t msgh_voucher_port;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
typedef struct{
mach_msg_size_t msgh_descriptor_count;
} mach_msg_body_t;
一条Mach消息实际上就是一个二进制数据包 (BLOB),其头部定义了当前端口local_port
和目标端口remote_port
。
发送和接受消息是通过同一个API进行的,其option
标记了消息传递的方向:
mach_msg_return_t
mach_msg(mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);
为了实现消息的发送和接收,mach_msg()
函数实际上是调用了一个Mac 陷阱 (trap),即函数mach_msg_trap()
,陷阱这个概念在Mach中等同于系统调用。当你在用户态调用mach_msg_trap()
时会触发陷阱机制,切换到内核态;内核态中内核实现的mach_msg()
函数会完成实际的工作,如下图:
这些概念可以参考维基百科: System_call、Trap_(computing)。
RunLoop的核心就是一个mach_msg()
(见上面代码的第7步),RunLoo调用这个函数去接收消息。如果没有别人发送 port
消息过来,内核会将线程置于等待状态。
例如在模拟器里跑起一个iOS的App,然后在App静止时点击暂停,你会看到主线程调用栈是停留在mach_msg_trap()
这个地方。
关于具体的如何利用mach port
发送信息,可以看看 NSHipster 这一篇文章,或者这里的中文翻译 。
关于Mach的历史可以看看这篇很有趣的文章:Mac OS X 背后的故事(三)Mach 之父 Avie Tevanian。
6. 苹果用RunLoop实现的功能
首先我们可以看一下App启动后RunLoop的状态:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSRunLoop *runloop = [NSRunLoop mainRunLoop];
NSLog(@"\n%@", runloop);
return YES;
}
//输出
<CFRunLoop 0x600000e7c000 [0x7fff80617cb0]>{wakeup port = 0x1903, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600003c26df0 [0x7fff80617cb0]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x7fff867f6c40 [0x7fff80617cb0]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x7fff8062b0a0 [0x7fff80617cb0]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x600003c7c3c0 [0x7fff80617cb0]>{type = mutable set, count = 14,
entries =>
0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
1 : <CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}
3 : <CFRunLoopSource 0x60000077c540 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003c7cde0, callout = __handleHIDEventFetcherDrain (0x7fff48126da3)}}
5 : <CFRunLoopSource 0x600000775800 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c450> {port = 430b, callback = 0x0}}
6 : <CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
0 : <0x7fcb05001038>
)}}
7 : <CFRunLoopObserver 0x6000003706e0 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff480bc354), context = <CFRunLoopObserver context 0x7fcb03401d10>}
8 : <CFRunLoopObserver 0x600000370640 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff480bc2eb), context = <CFRunLoopObserver context 0x7fcb03401d10>}
9 : <CFRunLoopSource 0x600000775980 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000326e4c0> {port = 5507, callback = 0x7fff2e3f7d2b}}
10 : <CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
0 : <0x7fcb05001038>
)}}
13 : <CFRunLoopSource 0x600000770240 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
14 : <CFRunLoopSource 0x6000007783c0 [0x7fff80617cb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001664a80, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff3662186a)}}
15 : <CFRunLoopSource 0x6000007758c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c4b0> {port = 4007, callback = 0x0}}
18 : <CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}
21 : <CFRunLoopSource 0x60000077c3c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000770480, callout = __handleEventQueue (0x7fff48126d97)}}
}
,
modes = <CFBasicHash 0x600003c26d90 [0x7fff80617cb0]>{type = mutable set, count = 3,
entries =>
0 : <CFRunLoopMode 0x6000009740d0 [0x7fff80617cb0]>{name = UITrackingRunLoopMode, port set = 0x2c03, queue = 0x600001c7c300, source = 0x600001c7c500 (not fired), timer port = 0x2d03,
sources0 = <CFBasicHash 0x600003c7c420 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
1 : <CFRunLoopSource 0x60000077c3c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000770480, callout = __handleEventQueue (0x7fff48126d97)}}
2 : <CFRunLoopSource 0x6000007783c0 [0x7fff80617cb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001664a80, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff3662186a)}}
6 : <CFRunLoopSource 0x60000077c540 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003c7cde0, callout = __handleHIDEventFetcherDrain (0x7fff48126da3)}}
}
,
sources1 = <CFBasicHash 0x600003c7c450 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
3 : <CFRunLoopSource 0x600000770240 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
4 : <CFRunLoopSource 0x6000007758c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c4b0> {port = 4007, callback = 0x0}}
5 : <CFRunLoopSource 0x600000775980 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000326e4c0> {port = 5507, callback = 0x7fff2e3f7d2b}}
6 : <CFRunLoopSource 0x600000775800 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c450> {port = 430b, callback = 0x0}}
}
,
observers = (
"<CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}",
"<CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}",
"<CFRunLoopObserver 0x600000370640 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff480bc2eb), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
"<CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x6000003706e0 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff480bc354), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
"<CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}"
),
timers = (null),
currently 606712521 (267350640984313) / soft deadline in: 1.84464767e+10 sec (@ -1) / hard deadline in: 1.84464767e+10 sec (@ -1)
},
1 : <CFRunLoopMode 0x6000009741a0 [0x7fff80617cb0]>{name = GSEventReceiveRunLoopMode, port set = 0x5303, queue = 0x600001c7c280, source = 0x600001c7c680 (not fired), timer port = 0x2f03,
sources0 = <CFBasicHash 0x600003c7c4e0 [0x7fff80617cb0]>{type = mutable set, count = 1,
entries =>
0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
}
,
sources1 = <CFBasicHash 0x600003c7c510 [0x7fff80617cb0]>{type = mutable set, count = 1,
entries =>
1 : <CFRunLoopSource 0x600000770180 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
}
,
observers = (null),
timers = (null),
currently 606712521 (267350643730266) / soft deadline in: 1.84464767e+10 sec (@ -1) / hard deadline in: 1.84464767e+10 sec (@ -1)
},
2 : <CFRunLoopMode 0x60000097c270 [0x7fff80617cb0]>{name = kCFRunLoopDefaultMode, port set = 0x2303, queue = 0x600001c70800, source = 0x600001c70900 (not fired), timer port = 0x2103,
sources0 = <CFBasicHash 0x600003c7c480 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
1 : <CFRunLoopSource 0x60000077c3c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000770480, callout = __handleEventQueue (0x7fff48126d97)}}
2 : <CFRunLoopSource 0x6000007783c0 [0x7fff80617cb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001664a80, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff3662186a)}}
6 : <CFRunLoopSource 0x60000077c540 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003c7cde0, callout = __handleHIDEventFetcherDrain (0x7fff48126da3)}}
}
,
sources1 = <CFBasicHash 0x600003c7c4b0 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
3 : <CFRunLoopSource 0x600000770240 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
4 : <CFRunLoopSource 0x6000007758c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c4b0> {port = 4007, callback = 0x0}}
5 : <CFRunLoopSource 0x600000775980 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000326e4c0> {port = 5507, callback = 0x7fff2e3f7d2b}}
6 : <CFRunLoopSource 0x600000775800 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c450> {port = 430b, callback = 0x0}}
}
,
observers = (
"<CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}",
"<CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}",
"<CFRunLoopObserver 0x600000370640 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff480bc2eb), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
"<CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x6000003706e0 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff480bc354), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
"<CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}"
),
timers = <CFArray 0x600001674960 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
0 : <CFRunLoopTimer 0x600000774540 [0x7fff80617cb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 606712522 (1.01609397 @ 267351663431855), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x7fff2576c7c2 / 0x7fff475e21aa) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore), context = <CFRunLoopTimer context 0x600002720f40>}
)},
currently 606712521 (267350643809969) / soft deadline in: 1.01962186 sec (@ 267351663431855) / hard deadline in: 1.01962181 sec (@ 267351663431855)
},
}
}
可以看到App运行到didFinishLaunchingWithOptions
时,系统默认注册了3个Mode:
- kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个Mode下运行的。
- UITrackingRunLoopMode: 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响。
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
我们常提到的还有两个Mode:
- kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
- UIInitializationRunLoopMode: 在刚启动App时第进入的第一个Mode,启动完成后就不再使用。
kCFRunLoopCommonModes会在之后加入,如果在某个ViewController内部打印时会看到。
UIInitializationRunLoopMode无论是main函数中,或者下RunLoop内部函数的符号断点都没看到过。
如果有人知道怎么回事望告知。
当RunLoop进行回调时,一般都是通过一个很长的函数调用出去 (call out)。当在代码中下断点调试时,通常能在调用栈上看到这些函数。
下面是这几个函数的整理版本,如果在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了:
{
/// 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.1 如果是被Timer唤醒的,回调Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9.2 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9.3 如果如果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);
}
6.1 AutoreleasePool
App启动后,苹果在主线程RunLoop里注册了两个Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()
。
// activities = 0xa0 BeforeWaiting | Exit
6 : <CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
0 : <0x7fcb05001038>
)}}
// activities = 0x1 Entry
10 : <CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
0 : <0x7fcb05001038>
)}}
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush()
创建自动释放池。其order是-2147483647
,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件:
BeforeWaiting(准备进入休眠) 时调用
_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧的池并创建新池。Exit(即将退出Loop) 时调用
_objc_autoreleasePoolPop()
来释放自动释放池。这个Observer的order是2147483647
,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被RunLoop创建好的AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建Pool了。
6.2 事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()
。
我们可以下一个``__IOHIDEventSystemClientQueueCallback`符号断点:
// 处理Source1的事件
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls)
{
CHECK_FOR_FORK();
Boolean sourceHandled = false;
/* Fire a version 1 source */
CFRetain(rls);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopSourceLock(rls);
if (__CFIsValid(rls)) {
__CFRunLoopSourceUnsetSignaled(rls);
__CFRunLoopSourceUnlock(rls);
__CFRunLoopDebugInfoForRunLoopSource(rls);
void *perform = rls->_context.version1.perform;
void *info = rls->_context.version1.info;
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_SOURCE1 | DBG_FUNC_START, rl, rlm, perform, info);
// 执行回调函数
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(perform, info);
...
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由IOKit.framework
生成一个IOHIDEvent
件并由SpringBoard
接收。这个过程的详细情况可以参考这里。
SpringBoard
只接收按键(锁屏/静音等)、触摸、加速、接近传感器等几种Event
,随后用mach port
转发给需要的App进程。随后苹果注册的那个Source1
就会触发回调,并调用_UIApplicationHandleEventQueue()
进行应用内部的分发。
_UIApplicationHandleEventQueue()
会把IOHIDEvent
处理并包装成UIEvent
进行处理或分发,其中包括识别UIGesture、处理屏幕旋、转发送给UIWindow
等。通常事件比如UIButton
点击、touchesBegin/Move/End/Cancel
事件都是在这个回调中完成的。
6.3手势识别
当上面的_UIApplicationHandleEventQueue()
识别了一个手势时,其首先会调用Cancel
将当前的 touchesBegin/Move/End
列回调打断。随后系统将对应的UIGestureRecognizer
标记为待处理。
1 : <CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}
苹果注册了一个Observer
监测BeforeWaiting (Loop即将进入休眠)
事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver()
,其内部会获取所有刚被标记为待处理的GestureRecognizer
,并执行GestureRecognizer
的回调。
当有UIGestureRecognizer
的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
6.4 界面更新
当在操作UI时,比如改变了frame、更新了UIView/CALayer的层次时,或者手动调用了UIView/CALayer的 setNeedsLayout/setNeedsDisplay
方法后,这个UIView/CALayer就被标记为待处理,并被提交到一个全局的容器去。
苹果注册了一个Observer监听BeforeWaiting(即将进入休眠)和Exit(即将退出Loop)事件
,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
。
这个函数里会遍历所有待处理的UIView/CAlayer以执行实际的绘制和调整,并更新UI界面。
18 : <CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}
这个函数内部的调用栈大概是这样的:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
QuartzCore:CA::Transaction::observer_callback:
CA::Transaction::commit();
CA::Context::commit_transaction();
CA::Layer::layout_and_display_if_needed();
CA::Layer::layout_if_needed();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA::Layer::display_if_needed();
[CALayer display];
[UIView drawRect];
6.5 定时器
-
NSTimer
其实就是
CFRunLoopTimerRef
,他们之间是toll-free bridged
的。一个NSTimer注册到 RunLoop 后,RunLoop会为其重复的时间点注册好事件。RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer有个属性叫做Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。
-
错过时间点的回调不会执行。
如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。
-
CADisplayLink
是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和NSTimer并不一样,其内部实际是操作了一个 Source)。
如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。Facebook开源的
AsyncDisplayLink
就是为了解决界面卡顿的问题,其内部也用到了RunLoop。
6.6 PerformSelecter
当调用NSObject的performSelecter:afterDelay:
,实际上其内部会创建一个Timer并添加到当前线程的RunLoop中。所以如果当前线程没有 RunLoop,则这个方法会失效。
6.7 关于GCD
实际上RunLoop底层也会用到GCD的东西,比如 RunLoop是用dispatch_source_t
实现的Timer。或者XNU
内核的mk_timer
。但同时 GCD 提供的某些接口也用到了RunLoop, 例如dispatch_async()
。
#if TARGET_OS_MAC || TARGET_OS_WIN32
#define USE_DISPATCH_SOURCE_FOR_TIMERS __HAS_DISPATCH__
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
当调用dispatch_async(dispatch_get_main_queue(), block)
时,libDispatch
会向主线程的RunLoop发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
里执行这个 block。
但这个逻辑仅限于dispatch到主线程,dispatch到其他线程仍然是由libDispatch
处理的。
6.8 关于网络请求
iOS 中,关于网络请求的接口自下至上有如下几层:
CFSocket
CFNetwork ->ASIHttpRequest
NSURLConnection ->AFNetworking
NSURLSession ->AFNetworking2, Alamofire
CFSocket是最底层的接口,只负责socket通信。
-
CFNetwork是基于CFSocket等接口的上层封装。
ASIHttpRequest工作于这一层。
-
NSURLConnection是基于CFNetwork的更高层的封装,提供面向对象的接口。
AFNetworking工作于这一层。
-
NSURLSession是iOS7中新增的接口,表面上是和NSURLConnection并列的,但底层仍然用到了NSURLConnection的部分功能 (比如
com.apple.NSURLConnectionLoader
线程)。AFNetworking2和 Alamofire工作于这一层。
下面主要介绍下NSURLConnection
的工作过程。
通常使用NSURLConnection
时,你会传入一个Delegate,当调用了[connection start]
后,这个Delegate就会不停收到事件回调。实际上,start
这个函数的内部会获取CurrentRunLoop,然后在其中的DefaultMode添加了4个 Source0 (即需要手动触发的Source)。
当开始网络传输时,我们可以看到NSURLConnection
创建了两个新线程:
-
com.apple.NSURLConnectionLoader
内部会使用RunLoop来接收底层socket的事件,并通过之前添加的Source0通知到上层的Delegate。
-
com.apple.CFSocket.private
处理底层 socket 连接的。
CFMultiplexerSource
是负责各种Delegate回调的,CFHTTPCookieStorage
是处理各种Cookie的。
NSURLConnectionLoader
中的RunLoop通过一些基于mach port
的Source1
接收来自底层CFSocket的通知。当收到通知后,其会在合适的时机向CFMultiplexerSource
等Source0
发送通知,同时主动唤醒Delegate线程的RunLoop来让其处理这些通知。CFMultiplexerSource
会在Delegate线程的RunLoop对Delegate执行实际的回调。
7. RunLoop的实际应用举例
7.1 AFNetworking
AFURLConnectionOperation 这个类是基于NSURLConnection
构建的,其希望能在后台线程接收Delegate回调。为此AFNetworking
单独创建了一个线程,并在这个线程中启动了一个RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
RunLoop启动前内部必须要有至少一个Timer/Observer/Source
,所以AFNetworking
在[runLoop run]
之前先创建了一个新的NSMachPort
添加进去了。通常情况下,调用者需要持有这个NSMachPort (mach_port)
并在外部线程通过这个port
发送消息到loop内;但此处添加port
只是为了让RunLoop不至于退出,并没有用于实际的发送消息。
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
当需要这个后台线程执行任务时,AFNetworking
通过调用[NSObject performSelector:onThread:..]
将这个任务扔到了后台线程的RunLoop中。
7.2 AsyncDisplayKit
AsyncDisplayKit 是Facebook推出的用于保持界面流畅性的框架,其原理大致如下:
UI 线程中一旦出现繁重的任务就会导致界面卡顿,这类任务通常分为3类:排版、绘制、UI对象操作。
- 排版通常包括计算视图大小、计算文本高度、重新计算子式图的排版等操作。
- 绘制一般有文本绘制 (例如 CoreText)、图片绘制 (例如预先解压)、元素绘制 (Quartz)等操作。
- UI对象操作通常包括UIView/CALayer等UI对象的创建、设置属性和销毁。
其中前两类操作可以通过各种方法扔到后台线程执行,而最后一类操作只能在主线程完成,并且有时后面的操作需要依赖前面操作的结果(例如TextView创建时可能需要提前计算出文本的大小)。AsyncDisplayKit所做的,就是尽量将能放入后台的任务放入后台,不能的则尽量推迟(例如视图的创建、属性的调整)。
为此,AsyncDisplayKit创建了一个名为ASDisplayNode
的对象,并在内部封装了UIView/CALayer,它具有和UIView/CALayer
相似的属性,例如frame、backgroundColor
等。所有这些属性都可以在后台线程更改,开发者可以只通过Node来操作其内部的UIView/CALayer,这样就可以将排版和绘制放入了后台线程。但是无论怎么操作,这些属性总需要在某个时刻同步到主线程的UIView/CALayer去。
AsyncDisplayKit仿照QuartzCore/UIKit
框架的模式,实现了一套类似的界面更新的机制:
在主线程的RunLoop中添加一个Observer,监听了kCFRunLoopBeforeWaiting和kCFRunLoopExit事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。
具体的代码可以看这里:_ASAsyncTransactionGroup。