这一篇文章主要在于 Run Loop 源码的阅读,内容有点长,须要一些基础。html
Run Loop 是一个 iOS 开发里的基础概念,它并不是独有的机制,不少系统和框架都有相似的实现,Run Loop 是 Event Loop (事件循环)机制的在 iOS 平台的一种实现。 查阅 wikipedia 有关 Event Loop 的描述:前端
在计算机科学里, Event Loop / Run Loop 是一个用于等待和发送消息/事件的程序结构,在程序中等待和派发一系列事件或者消息。它经过向“事件提供者”发出请求来工做,一般会阻塞请求,直到事件到达,而后调用相应的事件处理程序。linux
说到 Event Loop ,其实还应该了解到 Event-Driven (事件驱动)。git
Event-Driven 的出现,在于解决图形界面和用户交互的问题:github
一般 GUI 程序的交互事件执行是由用户来控制的,没法预测它发生的节点,对应这样的状况,须要采用 Event-Driven 的编程方法。编程
Event-Driven 的实现原理,基本就是使用 Event Loop 完成。Event-Driven 程序的执行,能够归纳成:windows
启动 ——> 事件循环(即等待事件发生并处理之)。数组
在 GUI 的设计场景下,通常写代码会是下面的思惟:安全
用户输入 -> 事件响应 -> 代码运行 -> 刷新页面状态bash
咱们一直在说 Event Loop 和 Event-Driven 。那什么是 Event (事件) 呢?
在 Event-Driven 中,能够把一切行为都抽象为 Event 。例如: IO 操做完成,用户点击按钮,一个图片加载完成,文本框的文字改变等等状况,均可以看做是一个 Event 。
当 Event 被放到 Event Loop 里进行处理的时候,会调用预先注册过的代码对 Event 作处理。这就叫 Event Handler 。
Event Handler 其实就是对 Event 的响应,能够叫作事件回调,事件处理,事件响应,都是同样的概念。
这里须要注意的是,一个 Event 并不必定有 Event Handler .
通常来讲,操做分为同步和异步。
同步操做,是一个接一个的处理。等前一个处理完,再执行下一个。那么在一些耗时任务上,好比有不少 I/O 操做 或者 网络请求 的任务,线程就会有长时间在等待任务结果。
异步操做,是不用等待执行结果的,能够直接在这期间执行另外的任务。等到任务结果出来以后,再进行处理。
实际上 Event Loop 就是实现异步的一种方法。
对于有回调的 Event,线程不用一直等待任务的结果出来再去执行下一个。而是等 Event 被加入到 Event Loop 时,再去执行。若是一个 Event 也没有,那线程就会休眠,避免浪费资源。
若是没有 Event Loop 来实现异步操做,那咱们的程序会很容易出现卡顿。
扩展 : JavaScript 在单线程条件下运行,能够完成异步操做,也是基于 Event Loop 机制。 建议能够参考 JavaScript异步编程 的内容来理解,更以帮助咱们举一反三,学习到通用的知识。
网上目前有关 Run Loop 的文章, 10 篇里面可能有 8 篇都是重复了 深刻理解RunLoop 中的代码。
然而这都是通过做者大量简化过的版本,隐藏了大量的细节。
其实从细节里面,咱们同样能够学习到不少东西,不妨尝试去阅读一下。
咱们知道 CFRunLoopRef 的代码是开源的,能够查看源代码来看它的实现,我选择的版本是 CF-1153.18 中的 CFRunLoop.c 。
因为苹果不容许咱们直接建立 RunLoop,只提供 2 个获取操做的函数:
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;
}
复制代码
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
复制代码
在两个函数里,都有使用了 CHECK_FOR_FORK() 。
它应该是属于多进程状况下的一个断言。
Threading Programming Guide 中,有这么一段话:
Warning: When launching separate processes using the fork function, you must always follow a call to fork with a call to exec or a similar function. Applications that depend on the Core Foundation, Cocoa, or Core Data frameworks (either explicitly or implicitly) must make a subsequent call to an exec function or those frameworks may behave improperly.
也就是说,当经过 fork 启动一个新进程的时候,你必需要接着调用一个 exec 或相似的函数。而依赖于 Core Founadtion / Cocoa / Core Data 框架的应用,必须调用 exec 函数,不然这些框架也许不能正确的工做。
因此为了保证安全,使用 CHECK_FOR_FORK 进行检查。
这里简单提一下 fork 。
在 UNIX 中,用 fork 来建立子进程,调用 fork( ) 的进程被称为父进程,新进程是子进程,而且几乎是父进程的彻底复制(变量、文件句柄、共享内存消息等相同,但 process id 不一样)。
由于子进程和父进程基本是同样的,要想让子进程去执行其余不一样的程序,子进程就须要调用 exec ,把自身替换为新的进程,其中process id不变,但原来进程的代码段、堆栈段、数据段被新的内容取代,来执行新的程序。
这样 fork 和 exec 就成为一种组合。
而在 iOS 这样的类 UNIX 系统里,基本上也都要经过 fork 的形式来建立新的进程。
假如没有执行完 exec ,那么执行的代码段等内容,仍是父进程里的,出现问题能够说百分之百。这就是 CHECK_FOR_FORK 检查的目的。
为了帮助理解,还须要先说 Thread-specific data (TSD),它能够叫做 线程私有数据 , 这个概念来自于 unix 之中。
它是存储和查询与某个线程相关数据的一种机制:
进程内的全部线程,共享进程的数据空间,所以全局变量为全部线程所共有。 而有时线程也须要保存本身的私有数据,这时能够建立线程私有数据(Thread-specific Data)TSD来解决。 在线程内部,私有数据能够被各个函数访问,但对其余线程是屏蔽的。例如咱们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每一个函数都应该能够调用它;但它又不能是一个全局变量。
在 Pthreads 里,把它叫作 Thread-local storage (线程私有存储) , 有如下几个相关的操做函数:
- pthread_key_create(): 分配用于标识进程中线程特定数据的pthread_key_t类型的键
- pthread_key_delete(): 销毁现有线程特定数据键
- pthread_setspecific(): 为指定线程的特定数据键设置绑定的值
- pthread_getspecific(): 获取调用线程的键绑定值,并将该绑定存储在 value 指向的位置中
复制代码
在苹果的平台上,基本就是利用上面操做实现 TSD 。
RunLoop 实际上就属于 TSD 的里存储的一种数据。因此咱们讲, RunLoop 和线程是一一对应的。而 RunLoop 会在线程销毁时,跟着一块儿清理,也是因为线程私有数据的机制。
TSD 对应存储的 key 有相关的析构函数,线程退出时,析构函数函数就会按照操做系统,实现定义的顺序被调用。因此在 CFSetTSD 会有一个析构函数的参数位置。
关于 CFGetTSD / CFSetTSD , 在 ForFoundationOnly.h 找到定义:
// ---- Thread-specific data --------------------------------------------
// Get some thread specific data from a pre-assigned slot.
CF_EXPORT void *_CFGetTSD(uint32_t slot);
// Set some thread specific data in a pre-assigned slot. Don't pick a random value. Make sure you're using a slot that is unique. Pass in a destructor to free this data, or NULL if none is needed. Unlike pthread TSD, the destructor is per-thread.
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, void (*destructor)(void *));
复制代码
TSD
也就是 thread specific data
的缩写了。
按照注释,说明 CFGetTSD
的做用是 -- 从预先赋值的位置,获得 TSD
。
上面也说明了 CFSetTSD
的做用 -- 在预先位置设置 TSD
。 这个数据不能够是随机的值,并保证你使用的位置有惟一性。若是须要释放这个数据,就传入析构函数;若是不须要释放,则传入NULL。和 pthread TSD 不一样的是,这一个析构函数是每个线程都有的。
在上面的CFRunLoopGetCurrent
里,是这么使用 _CFGetTSD 的:
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
复制代码
这里的 slot 值,通常对应的,都应该是相似 __CFTSDKeyRunLoop 的枚举类型的关键字。
在 CFPlatform.c 找到 CFGetTSD / CFSetTSD 具体的实现,发现二者其实都依靠了 CFTSDTable 类型的一个 table 实现。
CFTSDTable 是一个保存 TSD 数据的结构体:
// Data structure to hold TSD data, cleanup functions for each
typedef struct __CFTSDTable {
uint32_t destructorCount;
uintptr_t data[CF_TSD_MAX_SLOTS];
tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;
复制代码
它拥有两个数组: data 存储私有数据, destructors 存储释放函数 . 还有一个 destructorCount ,它顾名思义就是 destructors 数组的数量。
CFGetTSD 主要是取了 table ,获取 table 的 data 数组,按 slot 索引取值。
CFSetTSD 的做用,就是根据 CFTSDTable 的结构,分别是往 table 里设置 data 数组 slot 位置的值,以及 destructors 数组 slot 位置的值:
void *oldVal = (void *)table->data[slot];
table->data[slot] = (uintptr_t)newVal;
table->destructors[slot] = destructor;
复制代码
不管是 CFRunLoopGetMain 仍是 CFRunLoopGetCurrent ,二者调用了 CFRunLoopGet0 :
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {// kNilPthreadT 是一个静态变量为 0
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
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);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
复制代码
这里对主要的流程作一下解释:
注意:
在了解完 CFSetTSD 的做用, CFTSDKeyRunLoop 设置的意思就很清楚: 在 CFTSDKeyRunLoop 位置,存储 loop , 但不对 loop 设置析构函数。
直接对于 loop 的设置,其实这里已经完成了。
网络文章大部分,直接就说在 CFTSDKeyRunLoopCntr 设置了清理 loop 的回调。
对于为何能够释放 loop ,却避而不谈。
你们想过没有 :
在 CFTSDKeyRunLoopCntr 位置,给出的参数是
PTHREAD_DESTRUCTOR_ITERATIONS - 1
PTHREAD_DESTRUCTOR_ITERATIONS 表示的,是线程退出时,操做系统实现试图销毁线程私有数据的最大次数。试图销毁次数,和 CFFinalizeRunLoop 这个析构函数,是怎么关联起来的?又是怎么被调用的?
在前面,说过了 CFTSDTable ,实际上在 CFTSDGetTable() 里面,就作了相关 TSD 的设置:
// Get or initialize a thread local storage. It is created on demand.
static __CFTSDTable *__CFTSDGetTable() {
...
pthread_key_init_np(CF_TSD_KEY, __CFTSDFinalize);
...
}
复制代码
经过 CF_TSD_KEY ,指定了对应的析构函数 CFTSDFinalize 。
而 CFTSDFinalize 的代码以下:
static void __CFTSDFinalize(void *arg) {
// Set our TSD so we're called again by pthreads. It will call the destructor PTHREAD_DESTRUCTOR_ITERATIONS times as long as a value is set in the thread specific data. We handle each case below.
__CFTSDSetSpecific(arg);
if (!arg || arg == CF_TSD_BAD_PTR) {
// We've already been destroyed. The call above set the bad pointer again. Now we just return.
return;
}
// On first calls invoke destructor. Later we destroy the data.
// Note that invocation of the destructor may cause a value to be set again in the per-thread data slots. The destructor count and destructors are preserved.
// This logic is basically the same as what pthreads does. We just skip the 'created' flag.
for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) {
if (table->data[i] && table->destructors[i]) {
uintptr_t old = table->data[i];
table->data[i] = (uintptr_t)NULL;
table->destructors[i]((void *)(old));
}
}
if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) { // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
free(table);
// Now if the destructor is called again we will take the shortcut at the beginning of this function.
__CFTSDSetSpecific(CF_TSD_BAD_PTR);
return;
}
}
复制代码
咱们能够看到,table 会循环遍历 data 和 destructors 的数据,而且把 old 变量做为 destructors 里函数的参数。
这就是线程退出时,会调用到 Run Loop 销毁函数的缘由。
同时也因为 table 是从 0 开始遍历,因此会根据枚举值的大小,来决定销毁调用的顺序的。
咱们能够在 CFInternal.h 中找到相关枚举定义:
// Foundation uses 20-40
// Foundation knows about the value of __CFTSDKeyAutoreleaseData1
enum {
__CFTSDKeyAllocator = 1,
__CFTSDKeyIsInCFLog = 2,
__CFTSDKeyIsInNSCache = 3,
__CFTSDKeyIsInGCDMainQ = 4,
__CFTSDKeyICUConverter = 7,
__CFTSDKeyCollatorLocale = 8,
__CFTSDKeyCollatorUCollator = 9,
__CFTSDKeyRunLoop = 10,
__CFTSDKeyRunLoopCntr = 11,
__CFTSDKeyMachMessageBoost = 12, // valid only in the context of a CFMachPort callout
__CFTSDKeyMachMessageHasVoucher = 13,
// autorelease pool stuff must be higher than run loop constants
__CFTSDKeyAutoreleaseData2 = 61,
__CFTSDKeyAutoreleaseData1 = 62,
__CFTSDKeyExceptionData = 63,
};
复制代码
注释里有一句 autorelease pool stuff must be higher than run loop constants
的说明,这一点就其实关系到 Run Loop 和 autorelease pool 释放的顺序了。
/*! @abstract Compare and swap for <code>int</code> values. @discussion This function compares the value in <code>__oldValue</code> to the value in the memory location referenced by <code>__theValue</code>. If the values match, this function stores the value from <code>__newValue</code> into that memory location atomically. This function is equivalent to {@link OSAtomicCompareAndSwap32}. @result Returns TRUE on a match, FALSE otherwise. */
OSATOMIC_DEPRECATED_REPLACE_WITH(atomic_compare_exchange_strong)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0)
bool OSAtomicCompareAndSwapInt( int __oldValue, int __newValue, volatile int *__theValue );
复制代码
这一个函数,它首先对 oldValue , theValue 进行比较.
若是两个值相等,就执行 theValue = newValue,并返回 YES.
若是两个值不等,返回 NO .
值得注意的是,这个函数引入了 barrier,它的操做是原子的。 这是一个典型的 CAS 操做,无独有偶,在 RAC 中的一个特色也是使用 Atomic Operations ,完成线程同步。
它在CFRunLoopGet0
的做用是 : 比较 CFRunLoops 是否为 null 。 若是为 null (第一次建立)了,就把 dict 赋值给 CFRunLoops 。若是不为 null,就释放掉 dict 。
这里再稍微提一下 barrier , 上面说它保证了原子操做。
memory barrier 在维基的定义是:
内存屏障(英语:Memory barrier),也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操做中的一个同步点,使得此点以前的全部读写操做都执行后才能够开始执行此点以后的操做。 大多数现代计算机为了提升性能而采起乱序执行,这使得内存屏障成为必须。
而对于 Objective C 的实现来讲,几乎全部的加锁操做最后都会设置 memory barrier ,官方文档的解释:
Note: Most types of locks also incorporate a memory barrier to ensure that any preceding load and store instructions are completed before entering the critical section.
为了防止编译器对咱们的代码作优化,改变咱们代码的指令顺序,能够采用 barrier 设置对咱们的代码顺序作保证。
讲完 Run Loop 怎么获取,再看 Run Loop 怎么建立。
对于 CFRunLoopCreate :
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
CFRunLoopRef loop = NULL;
CFRunLoopModeRef rlm;
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
if (NULL == loop) {
return NULL;
}
(void)__CFRunLoopPushPerRunData(loop);
__CFRunLoopLockInit(&loop->_lock);
loop->_wakeUpPort = __CFPortAllocate();
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
__CFRunLoopSetIgnoreWakeUps(loop);
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
loop->_commonModeItems = NULL;
loop->_currentMode = NULL;
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
loop->_blocks_head = NULL;
loop->_blocks_tail = NULL;
loop->_counterpart = NULL;
loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
loop->_winthread = 0;
#endif
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}
复制代码
大致说一下建立的流程:
注意: CFRunLoopPushPerRunData 会在建立时作一些初始化设置, __CFPortAllocate() 会设置唤醒的端口, CFRunLoopSetIgnoreWakeUps 调用的缘由时,目前处于唤醒状态,对它的消息作忽略。 HALT 命令能够中止系统运行,假如 wakeUpPort 为 CFPORT_NULL
真正建立获得 CFRunLoopRef 类型的 loop ,调用的是 CFRuntimeCreateInstance
来建立的。
它是一个用来建立 CF 实例类型的函数:
CF_EXPORT CFTypeRef _CFRuntimeCreateInstance(CFAllocatorRef allocator, CFTypeID typeID, CFIndex extraBytes, unsigned char *category);
复制代码
更具体的解释能够查看 CFRuntime.h 对它的定义。
CFRunLoopCreate 给它传入了一个默认的分配器 kCFAllocatorSystemDefault ,一个 CFRunLoopGetTypeID() ,一个 size 。
CFRunLoopGetTypeID() 的操做以下:
CFTypeID CFRunLoopGetTypeID(void) {
static dispatch_once_t initOnce;
dispatch_once(&initOnce, ^{
__kCFRunLoopTypeID = _CFRuntimeRegisterClass(&__CFRunLoopClass);
__kCFRunLoopModeTypeID = _CFRuntimeRegisterClass(&__CFRunLoopModeClass);
});
return __kCFRunLoopTypeID;
}
复制代码
它在里面注册了 CFRunLoopClass 和 CFRunLoopModeClass 的类型,并用返回值,给对应的 typeID 赋值。做为单例,只运行一次。
size 的计算为 :
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
复制代码
size 是一个 CFRunLoop 类型自己的大小,减掉 CFRuntimeBase 类型的大小获得的结果。
为何要减去一个 CFRuntimeBase 的类型大小?
查看 CFRuntime.c 对源码,发现里面会把减掉的 sizeof(CFRuntimeBase) 再给加回来:
CFIndex size = sizeof(CFRuntimeBase) + extraBytes + (usesSystemDefaultAllocator ? 0 : sizeof(CFAllocatorRef));
复制代码
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create)
复制代码
CFRunLoopFindMode 是一个用来查找 mode 的函数,同时也能够来建立 mode 。
它其中有利用两个宏,来对 timer 的种类进行判断.查阅了一下定义:
#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
复制代码
也就是说,在 MACOSX 下,同时还会有使用 dispatch timer 来作定时器。而 MK_TIMER 是两个平台下都有的。
函数的大致逻辑是先判断有无,有就返回. 没有的话,就根据 create 的值决定是否新建立一个 mode .
在 CFRunLoopCreate 里面,调用的代码是
CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true)
复制代码
它会新建立一个 mode 返回。
启动 Run Loop 有 2 个函数,一个是 CFRunLoopRun
, 一个是 CFRunLoopRunInMode
:
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
复制代码
注:1.0e10,这个表示1.0乘以10的10次方,这个参数主要是规定RunLoop的时间,传这个时间,表示线程常驻。
主线程的RunLoop调用函数,就是使用了 CFRunLoopRun
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
复制代码
查看上面两个启动 Run Loop 运行的函数实现,发现都使用了 CFRunLoopRunSpecific
.
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
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;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
复制代码
1.经过 runloop 的 modeName 查找当前 mode。由于 CFRunLoopFindMode 的 create 参数为 false , 若是没找到,直接为 null ,不会建立新的 mode.
2.若是当前 mode 为空,函数结束,返回 CFRunLoopRunFinished .
这里比较奇怪的是
Boolean did = false
直接写死了 did 的值,后面又是return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished
. 怀疑 did 的值,应该还有一段代码是决定kCFRunLoopRunHandledSource
的结果,被苹果隐藏了没有开源出来。
3.若是当前 mode 存在,作一些赋值操做 .
4.向观察者发送 kCFRunLoopEntry 的消息,即将进入 RunLoop .
5.进入 CFRunLoopRun 函数,在这里作一系列观察和操做。
6.向观察者发送 kCFRunLoopExit 的消息,即将退出 RunLoop .
关于 CFRunLoopRun 在运行的时候,有 4 个枚举值表示它的状态:
/* Reasons for CFRunLoopRunInMode() to Return */
enum {
kCFRunLoopRunFinished = 1,//结束
kCFRunLoopRunStopped = 2,//暂停
kCFRunLoopRunTimedOut = 3,//超时
kCFRunLoopRunHandledSource = 4 //执行事件
};
复制代码
CFRunLoopDoObservers 的官方文档说明以下:
A CFRunLoopObserver provides a general means to receive callbacks at different points within a running run loop. In contrast to sources, which fire when an asynchronous event occurs, and timers, which fire when a particular time passes, observers fire at special locations within the execution of the run loop, such as before sources are processed or before the run loop goes to sleep, waiting for an event to occur. Observers can be either one-time events or repeated every time through the run loop’s loop.
Each run loop observer can be registered in only one run loop at a time, although it can be added to multiple run loop modes within that run loop.
一个 CFRunLoopObserver 提供了一个通用的方法,在不一样的时机去接受运行中的 runloop 的回调。与在一个异步事件发生时触发的源,和在特定时间以后触发的定时器相比,在 run loop 执行的过程当中, 观察者会在特定的位置发送信号,例如 sources 执行以前活着 run loop 将要休眠以前,等待事件的发生. 观察者能够是一次性的,或者在经过每次 run loop 的循环里重复。
每一个 run loop 观察者只能在 run loop 中注册一次,尽管它能够添加到该 run loop 内的多个 run loop mode 中。
这里其实有两个 CFRunLoopRun 函数,一个是暴露给咱们在外面使用的,不带参数的:
CF_EXPORT void CFRunLoopRun(void);
复制代码
如今要说的,是这一个:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) __attribute__((noinline));
复制代码
由于函数比较长,因此分段来进行讲解.
1.runloop 状态判断 / GCD 队列的端口设置:
//获取开始时间
uint64_t startTSR = mach_absolute_time();
//对 runloop 状态作判断,检查是否处于 stop 的状况
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//声明 dispatchPort 变量,做为一个 mach_port 通讯的端口,初始化值为 MACH_PORT_NULL
mach_port_name_t dispatchPort = MACH_PORT_NULL;
// 检测是否在主线程 && ( (是队列发的消息&&mode为null)||(不是队列发的消息&&不在主队列))
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
//若是是队列安全的,而且是主线程runloop,设置它对应的通讯端口
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
复制代码
#define HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY 0
复制代码
这里对于它的定义是 0 ,写死了。我猜想应该仍是有一个函数去作判断的。
目前只能从字面意思猜想,表明是否只分发 dispatch 消息的
#define _dispatch_get_main_queue_port_4CF _dispatch_get_main_queue_handle_4CF
复制代码
它是 dispatch_get_main_queue_handle_4CF 的宏,存在 libdispatch 中,里面对它的实现为:
dispatch_runloop_handle_t
_dispatch_get_main_queue_handle_4CF(void)
{
dispatch_queue_t dq = &_dispatch_main_q;
dispatch_once_f(&_dispatch_main_q_handle_pred, dq,
_dispatch_runloop_queue_handle_init);
return _dispatch_runloop_queue_get_handle(dq);
}
复制代码
返回的是主线程 runloop 所关联的的端口。
2.MACOSX 下,声明一个 mode 的队列通讯端口(在 MACOSX 环境中):
#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
复制代码
3.根据超时 seconds 的时长,作对应操做。
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
//小于等于 0 ,片刻的超时(instant timeout),直接设置为 0 ,不超时
if (seconds <= 0.0) {
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {//在限制的超时间隔内
//根据是否为主线程,设置队列是主队列仍是后台队列
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
//建立一个 GCD Timer
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
//把 timer 和 context 给关联起来
dispatch_set_context(timeout_timer, timeout_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);
//恢复唤起 timer 执行
dispatch_resume(timeout_timer);
} else {
//无限期超时
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
复制代码
4.进入 do - while 循环,直到 reVal 不为 0 。如下代码为更好理解,删去 windows 相关:
// 设置判断是否为最后一次 dispatch 的端口通讯的变量
Boolean didDispatchPortLastTime = true;
// 设置一个结果变量,最后为几个 CFRunLoopRunInMode 里返回状态之一。
int32_t retVal = 0;
do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// 一个状态变量,用于 消息状态 标志,初始值为 UNCHAMGED
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
//声明一个 msg_buffer 数组
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//声明和 mach port 有关的 port 和 msg 变量
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
// 声明一个类型为 CFPortSet 的 waitSet, 值为 run loop mode 里的 portSet.
__CFPortSet waitSet = rlm->_portSet;
//将 run loop 从忽略唤醒消息的状态 unset ,开始接受唤醒消息
__CFRunLoopUnsetIgnoreWakeUps(rl);
// 2. 通知 observers , Run Loop 即将触发 Timer 回调。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 3. 通知 observers , Run Loop 即将触发 Source0 (非 mach port) 回调。
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 执行 block
__CFRunLoopDoBlocks(rl, rlm);
// 4. 执行 Source0 (非 mach port) 。
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {// 执行完 source0 后,假如还有须要执行的,再执行一次 block
__CFRunLoopDoBlocks(rl, rlm);
}
// poll 变量,是否处理 source 或未超时
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
// 5. 若是有 Source1 (基于port) 处于 ready 状态
// 直接处理这个 Source1 而后跳转去处理消息(handle_msg)。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
}
didDispatchPortLastTime = false;
// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
// 注意到若是实际处理了 source0 或者超时,不会进入睡眠,因此不会通知
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 设置标志位, Run Loop 休眠
__CFRunLoopSetSleeping(rl);
// 使用 GCD 的话,将 GCD 端口加入监听端口集合中
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
// 休眠开始的时间,根据 poll 状态决定为 0 或者当前的绝对时间
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
// 7. 经过 CFRunLoopServiceMachPort 调用 mach_msg 休眠,等待被 mach_msg 消息唤醒
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// 若是在 MACOSX 中
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// 处理 GCD timer
do {
if (kCFUseCollectableAllocator) {//假若有kCFUseCollectableAllocator分配器,使用 memset 清空msg_buffer
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
// 设置 mach port 通讯,会睡眠线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// modeQueue 存在,并且为 livePort
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.
//执行 run loop mode 里的队列,直到队列都执行完成
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {//假如 _timerFired 为真,把 livePort 做为队列端口,在以前服务于 timers
rlm->_timerFired = false;
//退出
break;
} else {// _timerFired 为假, 而且 msg 存在不为 msg_buffer, 释放 msg
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
//退出
break;
}
} while (1);
#else // 不在 MACOSX 中
if (kCFUseCollectableAllocator) {//若是 kCFUseCollectableAllocator 分配器,使用 memset 清空 msg_buffer
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
//CFRunLoopServiceMachPort 会让线程休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
//上锁
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// 根据 poll 的值,记录休眠时间
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
//对 waitSet 里的 dispatchPort 端口作移除
__CFPortSetRemove(dispatchPort, waitSet);
//让 Run Loop 忽略唤醒消息,由于已经从新在运行了
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 8. 通知 observers: kCFRunLoopAfterWaiting, 线程刚被唤醒
// 注意实际处理过 source 0 或者已经超时的话,不会通知(由于没有睡)
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//处理对应唤醒的消息
handle_msg:;
//将 Run Loop 从新忽略唤醒消息,由于已经从新在运行了
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {// livePort 为空,什么事都不作
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {// livePort 等于 run loop 的 _wakeUpPort
// 被 CFRunLoopWakeUp 函数唤醒的
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
}
// 在 MACOSX 里
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {//livePort 等于 modeQueuePort
//9.1-1 被 timers 唤醒,处理 timers
CFRUNLOOP_WAKEUP_FOR_TIMER();
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) {//livePort 等于 run loop mode 的 _timerPort
// 9.1-2 被 timers 唤醒,处理 timers
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
else if (livePort == dispatchPort) {// livePort 等于 dispatchPort
// 9.2 若是有dispatch到main_queue的block,执行block
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
//解锁
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
//设置 CFTSDKeyIsInGCDMainQ 位置的 TSD 为 6 .
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
// 处理 msg
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
//设置 CFTSDKeyIsInGCDMainQ 位置的 TSD 为 0.
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
//上锁
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
//设置变量
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
//9.3 被 source (基于 mach port) 唤醒
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// 假如咱们 从这个 mach_msg 中接收到一个 voucher,而后在 TSD 中放置一个复制的新的 voucher.
// CFMachPortBoost 会在 TSD 中去查找这个 voucher.
// 经过使用 TSD 中的值,咱们将 CFMachPortBoost 绑定到这个接收到的 mach_msg 中,在这两段代码之间没有任何机会再次设置凭证
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {//若是 rls 存在
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
//处理 Source ,并返回执行结果
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {//发送reply消息(假如 reply 不为空)
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
//释放 reply 变量
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
}
// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
// 执行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
//根据一次循环后的状态,给 retVal 赋值 。状态不变则继续循环
if (sourceHandledThisLoop && stopAfterHandle) {
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)) {
retVal = kCFRunLoopRunFinished;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// 循环一次后收尾处理
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
} while (0 == retVal);
复制代码
上面的代码,有几个地方的定义可能须要结合其它地方才能理解:
/*! * @typedef voucher_mach_msg_state_t * * @abstract * Opaque object encapsulating state changed by voucher_mach_msg_adopt(). */
typedef struct voucher_mach_msg_state_s *voucher_mach_msg_state_t;
复制代码
不透明的对象封装状态由 voucher_mach_msg_adopt() 改变,它表明一种 mach_msg 通讯时的状态。
DISPATCH_EXPORT HANDLE _dispatch_get_main_queue_handle_4CF(void);
复制代码
返回做为主队列相关联的 run loop 。
5.释放 timerout_timer 定时器相关
if (timeout_timer) {//若是存在,取消并释放
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {//不存在,将对应的 timeour_context 释放
free(timeout_context);
}
//结束返回 retVal 状态。
return retVal;
复制代码
这个函数是让线程休眠的关键,它在里面作了和 mach port 相关的操做。
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
// msg 相关数据设置
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
// 根据 timeout 的值,决定 Run Loop 是休眠仍是执行
// timeout 为 TIMEOUT_INFINITY 时,才执行 CFRUNLOOP_SLEEP() 休眠
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
// 发送并接收 mach port 消息
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
// 在 mach_msg 以后注意全部 voucher 相关的正常运行
// 假如咱们没有释放前面的 voucher , 将会出现内存泄漏
voucher_mach_msg_revert(*voucherState);
// 会有调用者去负责调用 voucher_mach_msg_revert .它会让接收到的 voucher 变成当前的这一个值。
*voucherState = voucher_mach_msg_adopt(msg);
if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
// 调用者 在这里 请求了一个 voucher 的复制的值。经过在 mach_msg 先后作这个操做,咱们确保在 mach_msg 返回和使用 voucher 的复制值的时候,没有涉及设置 voucher 的值。
// 为确保 CFMachPortBoost 使用的 voucher ,因此咱们只在 voucher 不是 state_unchanged 的时候,去设置 TSD 。
*voucherCopy = voucher_copy();
} else {
//值为 VOUCHER_MACH_MSG_STATE_UNCHANGED ,置为 null
*voucherCopy = NULL;
}
}
// 唤醒 Run Loop
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {// ret 成功后,设置 livePort 的值,返回 true
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {// ret 超时,释放 msg ,有关变量置空,返回 false
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
//ret 不为 MACH_RCV_TOO_LARGE ,退出循环
if (MACH_RCV_TOO_LARGE != ret) break;
//ret 为 MACH_RCV_TOO_LARGE,作释放操做,从新进入循环
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
复制代码
这里有查询 CFRUNLOOP_SLEEP() 和 CFRUNLOOP_POLL() 等函数,都是 do { } while (0)
这样的宏,没有真正实现代码,因此没法再看到具体的状况。
这一次学习的过程,最大的感触,就是对于知识的相通性。
例如对于 TSD 线程私有数据的理解,搜寻不少跟 iOS 有关资料都找不到说明,最后是在 unix 相关的文章才看到解释。还有 Event Loop 的机制在其它平台等实现。
固然整个过程比较枯燥,阅读的量也比较大,须要耐心。
比较遗憾的是,有一些地方,苹果并无给出具体的代码实现或者明确的解释。
本人水平有限,若有错误和值得商榷的地方,欢迎你们拍砖。