RunLoop 源码阅读

前言

这一篇文章主要在于 Run Loop 源码的阅读,内容有点长,需要一些基础。

Event Loop

Run Loop 是一个 iOS 开发里的基础概念,它并非独有的机制,很多系统和框架都有类似的实现,Run Loop 是 Event Loop (事件循环)机制的在 iOS 平台的一种实现。
查阅 wikipedia 有关 Event Loop 的描述:

在计算机科学里, Event Loop / Run Loop 是一个用于等待和发送消息/事件的程序结构,在程序中等待和派发一系列事件或者消息。它通过向“事件提供者”发出请求来工作,通常会阻塞请求,直到事件到达,然后调用相应的事件处理程序。

Event Driven

说到 Event Loop ,其实还应该了解到 Event-Driven (事件驱动)。

Event-Driven 的出现,在于解决图形界面和用户交互的问题:

通常 GUI 程序的交互事件执行是由用户来控制的,无法预测它发生的节点,对应这样的情况,需要采用 Event-Driven 的编程方法。

Event-Driven 的实现原理,基本就是使用 Event Loop 完成。Event-Driven 程序的执行,可以概括成:

启动 ——> 事件循环(即等待事件发生并处理之)。

在 GUI 的设计场景下,一般写代码会是下面的思维:

用户输入 -> 事件响应 -> 代码运行 -> 刷新页面状态

Event

我们一直在说 Event Loop 和 Event-Driven 。那什么是 Event (事件) 呢?

在 Event-Driven 中,可以把一切行为都抽象为 Event 。例如: IO 操作完成,用户点击按钮,一个图片加载完成,文本框的文字改变等等情况,都可以看作是一个 Event 。

Event Handler

当 Event 被放到 Event Loop 里进行处理的时候,会调用预先注册过的代码对 Event 做处理。这就叫 Event Handler 。

Event Handler 其实就是对 Event 的响应,可以叫做事件回调,事件处理,事件响应,都是一样的概念。

这里需要注意的是,一个 Event 并不一定有 Event Handler .

Event Loop 解决了什么问题

一般来说,操作分为同步和异步。

同步操作,是一个接一个的处理。等前一个处理完,再执行下一个。那么在一些耗时任务上,比如有很多 I/O 操作 或者 网络请求 的任务,线程就会有长时间在等待任务结果。

异步操作,是不用等待执行结果的,可以直接在这期间执行另外的任务。等到任务结果出来之后,再进行处理。

实际上 Event Loop 就是实现异步的一种方法。

对于有回调的 Event,线程不用一直等待任务的结果出来再去执行下一个。而是等 Event 被加入到 Event Loop 时,再去执行。如果一个 Event 也没有,那线程就会休眠,避免浪费资源。

如果没有 Event Loop 来实现异步操作,那我们的程序会很容易出现卡顿。

扩展 :
JavaScript 在单线程条件下运行,可以完成异步操作,也是基于 Event Loop 机制。
建议可以参考 JavaScript异步编程 的内容来理解,更以帮助我们触类旁通,学习到通用的知识。

Run Loop 实现

网上目前有关 Run Loop 的文章, 10 篇里面可能有 8 篇都是重复了 深入理解RunLoop 中的代码。

然而这都是经过作者大量简化过的版本,隐藏了大量的细节。

其实从细节里面,我们一样可以学习到很多东西,不妨尝试去阅读一下。

我们知道 CFRunLoopRef 的代码是开源的,可以查看源代码来看它的实现,我选择的版本是 CF-1153.18 中的 CFRunLoop.c 。

获取 Run Loop

由于苹果不允许我们直接创建 RunLoop,只提供 2 个获取操作的函数:

  • CFRunLoopGetMain :
1
2
3
4
5
6
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;
}
  • CFRunLoopGetCurrent :
1
2
3
4
5
6
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}

CHECK_FOR_FORK()

在两个函数里,都有使用了 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

这里简单提一下 fork 。

在 UNIX 中,用 fork 来创建子进程,调用 fork( ) 的进程被称为父进程,新进程是子进程,并且几乎是父进程的完全复制(变量、文件句柄、共享内存消息等相同,但 process id 不同)。

因为子进程和父进程基本是一样的,要想让子进程去执行其他不同的程序,子进程就需要调用 exec ,把自身替换为新的进程,其中process id不变,但原来进程的代码段、堆栈段、数据段被新的内容取代,来执行新的程序。

这样 fork 和 exec 就成为一种组合。

而在 iOS 这样的类 UNIX 系统里,基本上也都要通过 fork 的形式来创建新的进程。

假如没有执行完 exec ,那么执行的代码段等内容,还是父进程里的,出现问题可以说百分之百。这就是 CHECK_FOR_FORK 检查的目的。

Thread-specific data

为了帮助理解,还需要先说 Thread-specific data (TSD),它可以叫作 线程私有数据 , 这个概念来自于 unix 之中。

它是存储和查询与某个线程相关数据的一种机制:

进程内的所有线程,共享进程的数据空间,因此全局变量为所有线程所共有。
而有时线程也需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Data)TSD来解决。
在线程内部,私有数据可以被各个函数访问,但对其他线程是屏蔽的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量。

Pthreads 里,把它叫做 Thread-local storage (线程私有存储) , 有以下几个相关的操作函数:

1
2
3
4
- pthread_key_create(): 分配用于标识进程中线程特定数据的pthread_key_t类型的键
- pthread_key_delete(): 销毁现有线程特定数据键
- pthread_setspecific(): 为指定线程的特定数据键设置绑定的值
- pthread_getspecific(): 获取调用线程的键绑定值,并将该绑定存储在 value 指向的位置中

在苹果的平台上,基本就是利用上面操作实现 TSD 。

RunLoop 实际上就属于 TSD 的里存储的一种数据。所以我们讲, RunLoop 和线程是一一对应的。而 RunLoop 会在线程销毁时,跟着一起清理,也是由于线程私有数据的机制。

TSD 对应存储的 key 有相关的析构函数,线程退出时,析构函数函数就会按照操作系统,实现定义的顺序被调用。所以在 CFSetTSD 会有一个析构函数的参数位置。

CFGetTSD / CFSetTSD

关于 CFGetTSD / CFSetTSD , 在 ForFoundationOnly.h 找到定义:

1
2
3
4
5
6
7
// ---- 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 的:

1
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);

这里的 slot 值,一般对应的,都应该是类似 __CFTSDKeyRunLoop 的枚举类型的关键字。

CFTSDTable

CFPlatform.c 找到 CFGetTSD / CFSetTSD 具体的实现,发现两者其实都依靠了 CFTSDTable 类型的一个 table 实现。

CFTSDTable 是一个保存 TSD 数据的结构体:

1
2
3
4
5
6
// 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 位置的值:

1
2
3
4
void *oldVal = (void *)table->data[slot];

table->data[slot] = (uintptr_t)newVal;
table->destructors[slot] = destructor;

CFRunLoopGet0

无论是 CFRunLoopGetMain 还是 CFRunLoopGetCurrent ,两者调用了 CFRunLoopGet0 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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;
}

这里对主要的流程做一下解释:

  1. 第一次进入,无论 t 为主线程或者子线程。因为 __CFRunLoops 为 null ,所以会创建一个 mainLoop.
  2. 根据传递进来的 t ,创建对应的 loop 。t 作为 key,loop 作为 value ,存储到 __CFRunLoops 里。如果已经有了对应 loop 存在,则不创建。
  3. 判断 t 是否为当前线程。如果是当前线程,就会利用 CFSetTSD 在 CFTSDKeyRunLoop/CFTSDKeyRunLoopCntr 的位置做设置。

注意:

在了解完 CFSetTSD 的作用, CFTSDKeyRunLoop 设置的意思就很清楚: 在 CFTSDKeyRunLoop 位置,存储 loop , 但不对 loop 设置析构函数。

直接对于 loop 的设置,其实这里已经完成了。

网络文章大部分,直接就说在 CFTSDKeyRunLoopCntr 设置了清理 loop 的回调。

对于为什么可以释放 loop ,却避而不谈。

大家想过没有 :

在 CFTSDKeyRunLoopCntr 位置,给出的参数是 PTHREAD_DESTRUCTOR_ITERATIONS - 1
PTHREAD_DESTRUCTOR_ITERATIONS 表示的,是线程退出时,操作系统实现试图销毁线程私有数据的最大次数。

试图销毁次数,和 CFFinalizeRunLoop 这个析构函数,是怎么关联起来的?又是怎么被调用的?

在前面,说过了 CFTSDTable ,实际上在 CFTSDGetTable() 里面,就做了相关 TSD 的设置:

1
2
3
4
5
6
// 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 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 中找到相关枚举定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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 释放的顺序了。

OSAtomicCompareAndSwapPtrBarrier

1
2
3
4
5
6
7
8
9
10
11
12
13
/*! @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 。

memory barrier

这里再稍微提一下 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 设置对我们的代码顺序做保证。

CFRunLoopCreate

讲完 Run Loop 怎么获取,再看 Run Loop 怎么创建。

对于 CFRunLoopCreate :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
}

大体说一下创建的流程:

  1. 通过 CFRuntimeCreateInstance ,创建一个 CFRunLoopRef 实例。
  2. 对 loop 做初始化设置,比如唤醒端口,commonModes 等的设置。

注意:
CFRunLoopPushPerRunData 会在创建时做一些初始化设置,
__CFPortAllocate() 会设置唤醒的端口,
CFRunLoopSetIgnoreWakeUps 调用的原因时,目前处于唤醒状态,对它的消息做忽略。
HALT 命令可以停止系统运行,假如 wakeUpPort 为 CFPORT_NULL

CFRuntimeCreateInstance

真正创建得到 CFRunLoopRef 类型的 loop ,调用的是 CFRuntimeCreateInstance 来创建的。

它是一个用来创建 CF 实例类型的函数:

1
CF_EXPORT CFTypeRef _CFRuntimeCreateInstance(CFAllocatorRef allocator, CFTypeID typeID, CFIndex extraBytes, unsigned char *category);

更具体的解释可以查看 CFRuntime.h 对它的定义。

CFRunLoopCreate 给它传入了一个默认的分配器 kCFAllocatorSystemDefault ,一个 CFRunLoopGetTypeID() ,一个 size 。

CFRunLoopGetTypeID() 的操作如下:

1
2
3
4
5
6
7
8
CFTypeID CFRunLoopGetTypeID(void) {
static dispatch_once_t initOnce;
dispatch_once(&initOnce, ^{
__kCFRunLoopTypeID = _CFRuntimeRegisterClass(&__CFRunLoopClass);
__kCFRunLoopModeTypeID = _CFRuntimeRegisterClass(&__CFRunLoopModeClass);
});
return __kCFRunLoopTypeID;
}

它在里面注册了 CFRunLoopClass 和 CFRunLoopModeClass 的类型,并用返回值,给对应的 typeID 赋值。作为单例,只运行一次。

size 的计算为 :

1
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);

size 是一个 CFRunLoop 类型本身的大小,减掉 CFRuntimeBase 类型的大小得到的结果。

为什么要减去一个 CFRuntimeBase 的类型大小?

查看 CFRuntime.c 对源码,发现里面会把减掉的 sizeof(CFRuntimeBase) 再给加回来:

1
CFIndex size = sizeof(CFRuntimeBase) + extraBytes + (usesSystemDefaultAllocator ? 0 : sizeof(CFAllocatorRef));

CFRunLoopFindMode

1
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create)

CFRunLoopFindMode 是一个用来查找 mode 的函数,同时也可以来创建 mode 。

它其中有利用两个宏,来对 timer 的种类进行判断.查阅了一下定义:

1
2
3
4
5
6
7
#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 里面,调用的代码是

1
CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true)

它会新创建一个 mode 返回。

运行 Run Loop

启动 Run Loop 有 2 个函数,一个是 CFRunLoopRun , 一个是 CFRunLoopRunInMode

  • DefaultMode 启动
1
2
3
4

void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}

注:1.0e10,这个表示1.0乘以10的10次方,这个参数主要是规定RunLoop的时间,传这个时间,表示线程常驻。

主线程的RunLoop调用函数,就是使用了 CFRunLoopRun

  • 指定 Mode 启动,允许设置RunLoop超时时间
1
2
3
4

int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

查看上面两个启动 Run Loop 运行的函数实现,发现都使用了 CFRunLoopRunSpecific .

CFRunLoopRunSpecific

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 个枚举值表示它的状态:

1
2
3
4
5
6
7
/* Reasons for CFRunLoopRunInMode() to Return */
enum {
kCFRunLoopRunFinished = 1,//结束
kCFRunLoopRunStopped = 2,//暂停
kCFRunLoopRunTimedOut = 3,//超时
kCFRunLoopRunHandledSource = 4 //执行事件
};

CFRunLoopDoObservers

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

这里其实有两个 CFRunLoopRun 函数,一个是暴露给我们在外面使用的,不带参数的:

1
CF_EXPORT void CFRunLoopRun(void);

现在要说的,是这一个:

1
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) __attribute__((noinline));

因为函数比较长,所以分段来进行讲解.

1.runloop 状态判断 / GCD 队列的端口设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//获取开始时间
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();
  • HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY
1
#define HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY 0

这里对于它的定义是 0 ,写死了。我猜测应该还是有一个函数去做判断的。

目前只能从字面意思猜测,代表是否只分发 dispatch 消息的

  • _dispatch_get_main_queue_port_4CF
1
#define _dispatch_get_main_queue_port_4CF _dispatch_get_main_queue_handle_4CF

它是 dispatch_get_main_queue_handle_4CF 的宏,存在 libdispatch 中,里面对它的实现为:

1
2
3
4
5
6
7
8
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 环境中):

1
2
3
4
5
6
7
8
9
#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 的时长,做对应操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
   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 相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    // 设置判断是否为最后一次 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);

```

上面的代码,有几个地方的定义可能需要结合其它地方才能理解:

- voucher_mach_msg_state_t 在 [mach.h](https://opensource.apple.com/source/xnu/xnu-2782.30.5/libsyscall/mach/mach/mach.h) 中:

```objc
/*!
* @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 通信时的状态。

  • HANDLE:
1
DISPATCH_EXPORT HANDLE _dispatch_get_main_queue_handle_4CF(void);

返回作为主队列相关联的 run loop 。

  • memset :作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。

5.释放 timerout_timer 定时器相关

1
2
3
4
5
6
7
8
if (timeout_timer) {//如果存在,取消并释放
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {//不存在,将对应的 timeour_context 释放
free(timeout_context);
}
//结束返回 retVal 状态。
return retVal;

CFRunLoopServiceMachPort

这个函数是让线程休眠的关键,它在里面做了和 mach port 相关的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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 的机制在其它平台等实现。

比较遗憾的是,有一些地方,苹果并没有给出具体的代码实现或者明确的解释。

参考

前端思维转变–从事件驱动到数据驱动

苹果文档–RunLoop

CFRunLoop.c 源码

XNU 源码

线程私有数据

UNIX环境高级编程——线程私有数据