从源码读懂 Runloop

runloop 是和线程密切相关的一个组件,它帮助线程管理需要被处理的事件和消息,例如网络连接,异步回调,定时器…
当有事件或消息要处理时唤醒线程处理,否则休眠等待接收 mach 消息。

runloop 提供了一个入口函数 CFRunLoopRun()。当线程执行这个函数,runloop 就会在默认 mode 下一直处于函数内部 接收消息->等待->处理 的循环中,就像它的名字一样 ‘run loop’,在一直跑圈。
正在情况下,当休眠时间超时或者当前 mode 中一个 mode item(observer/timer/source) 都没有,则 runloop 会直接退出,不再进入循环。
main runloop 的超时时间被设定为 DISPATCH_TIME_FOREVER,且系统会在应用启动时向默认 mode 注册许多的 mode item,这意味着 main runloop 将永远的执行下去,这保证了当前应用能够随时的响应用户时间,但却不一直占用 CPU 资源

runloop 的核心是 mach_msg() 函数。
使用这个函数,你可以向指定的 mach port 发送消息或者接受到 other 向某个 mach port 发送的消息,区别在于函数的参数 option 是什么值。在等待过程中,runloop 处于休眠状态且不占用 cpu 资源。

以下内容摘抄自 深入理解RunLoop
为了实现消息的发送和接收,mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图:

本文所用的源码为 Swift 版本的 CoreFoundation 源码 https://github.com/apple/swift-corelibs-foundation/

runloop 与线程的关系

每个线程都有一个对应的 runloop。苹果不允许我们直接创建 runloop ,但他提供了两个函数来获取 runloop:CFRunLoopGetCurrent() 和 CFRunLoopGetMain()。
这两个函数的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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() 这个宏应该是用来检查是否进程复制,相关内容我不大明白,这里略过。

可以看到这两个函数后面都调用了 _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
44
45
46
47
48
49
50
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
/// kNilPthreadT == (pthread *)0x0
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
/// 创建缓存字典。随后创建主线程的 run loop,并且以 key(thread)/value(runloop) 的形式保存到缓存字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
/// 将 main runloop 保存到缓存字典中,其中 key 为主线程
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
}

CFRunLoopRef newLoop = NULL;
/// 在缓存中查找 runloop 是否已经创建
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

if (!loop) {
/// 如果 runloop 不存在则新建一个,并保存到缓存中
newLoop = __CFRunLoopCreate(t);

cf_trace(KDEBUG_EVENT_CFRL_LIFETIME|DBG_FUNC_START, newLoop, NULL, NULL, NULL);
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())) {
/// TSD means thread specific data
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
/// PTHREAD_DESTRUCTOR_ITERATIONS 的定义为 4
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
#else
_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
}
}
return loop;
}

_CFRunLoopGet0 函数大致做了下面几样事情

  • 进入到这个函数,首先会判断缓存字典 dict 是否存在,如果不存在则创建
  • 随后在缓存中查找 main runloop 是否存在,若不存在则创建 main runloop,并以 key(thread)/value(runloop) 的形式保存到缓存中,
  • 接着在缓存中查找当前线程对应的 runloop 是否存在,若不存在则创建,并保存到缓存中

在 _CFRunLoopGet0 函数结尾部分可以看到 _CFSetTSD()_CFGetTSD() 这两个函数的出现

TSD 是 thread specific data 的缩写,你也可以叫它为 TLS(Thread-Local Storage)。它是线程私有的数据变量,其它线程无法访问,但同一个线程中的任意函数都可以访问到

下面是它在内存管理中的一个应用:
我们知道非 alloc/new/copy/mutableCopy 方法创建的对象,需要先对其 autorelease,随后变量持有时再对其 retain。既然一加一减,那为什么不让变量持有对象呢?于是 ARC 为了优化,使用了objc_autoreleaseReturnValue() 和 objc_retainAutoreleasedReturnValue() 两个函数,在 autorelease 时在 TLS 中存入一个标记,在 retain 时从 TLS 取出这个标记,该标记即用来表示是否使用优化,如果使用,则跳过 autorelease 和 retain 两个步骤。

使用 TSD 时,

  • 我们需要先为其开辟一块内存空间,例如使用 calloc() 函数
  • 使用 pthread_key_create(pthread_key_t *key, void (*destructor)(void *)); 函数,得到一个 pthread_key_t 类型的 key,destructor 函数的作用是线程结束时销毁该快内存。当 key 被创建出来后,任意线程都能访问它,但需要各自为它绑定不同的值
  • 使用 pthread_setspecific(__CFTSDIndexKey, arg) 函数,将 key 与第一步中创建的内存空间绑定

因为每个线程能够拥有的 TSD 数目是有限的,为了节约,所以你可以将 key 与一个数据结构(数组/字典)绑定起来保存在 TSD 中。
在 runloop 中,key 对应的数据结构是 __CFTSDTable 在 TSD 。__CFTSDTable 有两个数组类型的成员变量:datadestructors。其中 data 用来保存数据,destructors 保存的函数在每次线程退出时调用。数组的容量是一个固定值:CF_TSD_MAX_SLOTS(70)
_CFSetTSD() 函数中,runloop 在数组下标为 __CFTSDKeyRunLoopCntr(3) 的位置插入数据 (PTHREAD_DESTRUCTOR_ITERATIONS/4/-1) 和函数 __CFFinalizeRunLoop 。当每次线程退出时,调用 __CFFinalizeRunLoop 函数,将下标 __CFTSDKeyRunLoopCntr 对应的值 -1,变为 0 时将 runloop 从缓存中清除

runloop,mode 以及 mode item

一个 runloop 可以包含若干 mode,每个 mode 又包含若干 Source/Timer/Observer。
但是 runloop 每次只能指定一个 mode 运行,处理该 Mode 里面的 Source/Timer/Observer 事件,这个 mode 被称作 CurrentMode。如果需要切换 mode,可以强制当前 mode 退出(例如 CFRunLoopStop() 函数),然后再指定别的 mode 进入。
苹果的这种设定,我的理解是为了优化效率:将不同的事件归类到不同的 mode 当中,线程只能处理某个 mode 的事件而不是所有的事件

函数 CFRunLoopStop() 可以使得 runloop 强制退出
CFRunLoopStop() 函数的作用是将当前 mode 标记为 stopped 状态,然后发送 mach 消息给当前 mode,runloop 被唤醒后识别出 mode 的 stopped 状态,随即退出循环。

CFRunLoop 的结构如下:

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
struct __CFRunLoop {
/// 类似于 isa
CFRuntimeBase _base;
_CFRecursiveMutex _lock; /* locked for accessing mode list */
/// other 可以向该 mach port 发送消息以唤醒 runloop
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
/// 用来记录每次运行主函数时的一次信息,每次进入主函数时重置该属性
volatile _per_run_data *_perRunData; // reset for runs of the run loop
/// runloop 对应的线程
_CFThreadRef _pthread;
/// 线程,windows 平台下用到的,
uint32_t _winthread;
/// common 类型的 mode 集合
CFMutableSetRef _commonModes;
/// 被添加到 kCFRunLoopCommonModes mode 中的 mode item 集合
CFMutableSetRef _commonModeItems;
/// runloop 当前运行的 mode
CFRunLoopModeRef _currentMode;
/// runloop 拥有的 mode 集合,Set
CFMutableSetRef _modes;
/// 添加到 runloop 的 block,链表结构,这个是头
struct _block_item *_blocks_head;
/// 添加到 runloop 的 block,链表结构,这个是尾
struct _block_item *_blocks_tail;
/// runloop 运行时的时间戳,但源码内未见其实用 ?
CFAbsoluteTime _runTime;
/// runloop 累计的休眠时间
CFAbsoluteTime _sleepTime;
/// 对应的 NSRunLoop 对象
CFTypeRef _counterpart;
/// 貌似是用来标记是否在当前线程销毁别的线程的 runloop
_Atomic(uint8_t) _fromTSD;
/// 设置定时器时用到
CFLock_t _timerTSRLock;
};

CFRunLoop 的构建函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static CFRunLoopRef __CFRunLoopCreate(_CFThreadRef 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);
_CFRecursiveMutexCreate(&loop->_lock);
loop->_wakeUpPort = __CFPortAllocate((uintptr_t)loop);
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
__CFRunLoopSetIgnoreWakeUps(loop);
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
loop->_pthread = t;
loop->_timerTSRLock = CFLockInit;
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}

注意到 runloop 使用 loop->_wakeUpPort = __CFPortAllocate((uintptr_t)loop); 代码为其生成了一个 wake up port。就像申请了一个 qq 号一样,你可以登录这个账号接收到别人发给你的消息,在这里,runloop 使用这个 mach port 来接受被 wake up 的消息
同时,runloop 还会创建一个 kCFRunLoopDefaultMode 默认类型的 mode

__CFRunLoopMode 结构如下:

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
struct __CFRunLoopMode {
CFRuntimeBase _base;
_CFRecursiveMutex _lock; /* must have the run loop locked before locking this */
/// mode name,例如 kCFRunLoopDefaultMode
CFStringRef _name;
/// 标记 mode 是否停止
Boolean _stopped;
/// 为了内存对齐
char _padding[3];
/// sources0 集合,Set
CFMutableSetRef _sources0;
/// sources1 集合,Set
CFMutableSetRef _sources1;
/// observer 集合,Array
CFMutableArrayRef _observers;
/// timer 集合,Array
CFMutableArrayRef _timers;
/// 字典,映射关系 port -> sources1
CFMutableDictionaryRef _portToV1SourceMap;
/// 可能接受到 mach 消息的 mach port 集合
__CFPortSet _portSet;
/// observer 注册的观察时间点
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
/// dispatch_source_t 类型的定时器
dispatch_source_t _timerSource;
/// dispatch_source_t 定时器运行的 dispatch_queue_t
dispatch_queue_t _queue;/// Run Loop Mode Queue
/// dispatch_source_t 定时器是否被触发
Boolean _timerFired; // set to true by the source when a timer has fired
/// 是否设置了 dispatch_source_t 定时器
Boolean _dispatchTimerArmed;
#endif
/// mk_timer 定时器触发后发送消息的 mach port
__CFPort _timerPort;
/// 是否设置了 mk_timer 定时器
Boolean _mkTimerArmed;
/// 注册在 rlm 的 timer 中,最早的触发时间戳,单位为纳秒
uint64_t _timerSoftDeadline; /* TSR */
/// 注册在 rlm 的 timer 中,最早的触发时间戳 + 宽余量(tolerance),单位为纳秒
uint64_t _timerHardDeadline; /* TSR */
};

Source0/Source1/Timer/Observer 被统称为 mode item,一个 mode item 可以被同时加入多个 mode,但重复加入同一个 mode 时是不会有效果的。Source0/Source1 可以注册到多个 runloop 中,而 Timer/Observer 只能注册在同一个 runloop
如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

有一个特殊的 mode,名字叫做 kCFRunLoopCommonModes,Foundation 中叫 NSRunLoopCommonModes。
说它特殊是因为 runloop 并不能在 mode 下运行,但是你却可以往它里面添加 mode item。mode item 添加后会放在 runloop->_commonModeItems 中,并同步到 ”common“ 类型的 mode 当中。我们熟知的 kCFRunLoopDefaultMode 和 UITrackingRunLoopMode 即是 ”common“ 类型。

我们可以看到 mode 的结构体中定义了两种类型的的定时器(NSTimer 我会称呼为 timer,底层的定时器称呼为 定时器,NSTimer 由 底层定时器实现,后面不在重复):

  • dispatch_source_t _timerSource
  • mk_timer

实际上,NSTimer 由这两种定时器实现,具体使用哪一种根据创建 NSTimer 时指定的 tolerance(宽余量) 决定。
如果 tolerance 为 0 则使用 mk_timer,否则使用 dispatch_source_t。我猜测是因为 mk_timer 精度更高但是不能响应 tolerance,所以在 tolerance 不为 0 的情况下使用 dispatch_source_t。
这一部分在将 CFRunLoopTimerRef 这一节时还会介绍

__CFRunLoopMode 的构建函数如下,直接跳过也行

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
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
CHECK_FOR_FORK();
CFRunLoopModeRef rlm;
struct __CFRunLoopMode srlm;
memset(&srlm, 0, sizeof(srlm));
_CFRuntimeSetInstanceTypeIDAndIsa(&srlm, _kCFRuntimeIDCFRunLoopMode);
srlm._name = modeName;
rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
if (NULL != rlm) {
__CFRunLoopModeLock(rlm);
return rlm;
}
if (!create) {
return NULL;
}
rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, _kCFRuntimeIDCFRunLoopMode, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
if (NULL == rlm) {
return NULL;
}
_CFRecursiveMutexCreate(&rlm->_lock);
rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
rlm->_portSet = __CFPortSetAllocate();
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;

kern_return_t ret = KERN_SUCCESS;
#if TARGET_OS_MAC
#if USE_DISPATCH_SOURCE_FOR_TIMERS
rlm->_timerFired = false;
rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);
mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***", -1);
rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);

__block Boolean *timerFiredPointer = &(rlm->_timerFired);
dispatch_source_set_event_handler(rlm->_timerSource, ^{
*timerFiredPointer = true;
});

// Set timer to far out there. The unique leeway makes this timer easy to spot in debug output.
dispatch_source_set_timer(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
dispatch_resume(rlm->_timerSource);

ret = __CFPortSetInsert(queuePort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);

#endif
#endif
rlm->_timerPort = mk_timer_create();
if (rlm->_timerPort == MACH_PORT_NULL) {
CRASH("*** Unable to create timer Port (%d) ***", rlm->_timerPort);
}
ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);

ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);

#if TARGET_OS_WIN32
rlm->_msgQMask = 0;
rlm->_msgPump = NULL;
#endif
CFSetAddValue(rl->_modes, rlm);
CFRelease(rlm);
__CFRunLoopModeLock(rlm); /* return mode locked */
return rlm;
}

这里有一部分代码比较重要,这里我把它摘出来

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
 rlm->_timerFired = false;
rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);
mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***", -1);
rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);

__block Boolean *timerFiredPointer = &(rlm->_timerFired);
dispatch_source_set_event_handler(rlm->_timerSource, ^{
*timerFiredPointer = true;
});

// Set timer to far out there. The unique leeway makes this timer easy to spot in debug output.
dispatch_source_set_timer(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
dispatch_resume(rlm->_timerSource);

ret = __CFPortSetInsert(queuePort, rlm->_portSet);


rlm->_timerPort = mk_timer_create();
if (rlm->_timerPort == MACH_PORT_NULL) {
CRASH("*** Unable to create timer Port (%d) ***", rlm->_timerPort);
}
ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);

ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);

首先是 dispatch_source_t 部分:

为了让 dispatch_source_t 定时器能够开启,我们为当前 mode 创建了一个 dispatch_queue_t queue,这个 queue 用来执行定时器任务,并且为定时器触发设定了一个回调 ^{ *_timerFired = true; });,即当定时器触发后将 _timerFired 设定为 true。
每个 dispatch_queue_t 都对应着一个 mach port,随后我们将其添加到 _portSet 中

然后是 mk_timer 部分

为了让 mk_timer 触发后能够通知到 mode,我们为 mode 申请了一个 mach port mk_timer_create,并且将其添加到 _portSet 中

CFRunLoopObserverRef

CFRunLoopObserverRef 观察者。
Observer 可以观测的时间点有以下几个:

1
2
3
4
5
6
7
8
9
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 // 所有时间点
};

CFRunLoopObserverRef 的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct __CFRunLoopObserver {
CFRuntimeBase _base;
_CFRecursiveMutex _lock;
CFRunLoopRef _runLoop;
/// 表示注册到了多少个 mode 中
CFIndex _rlCount;
/// 观测时间点
CFOptionFlags _activities; /* immutable */
/// 优先级,数值越低表示优先级越高
CFIndex _order; /* immutable */
/// 回调
CFRunLoopObserverCallBack _callout; /* immutable */
/// 回调执行需要的上下文
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};

Observer 的结构定义比较简单,这里也不分析看看注释就好了。
我们可以使用 CFRunLoopAddObserver() 向 mode 添加 Observer,实现就不贴了(自己可以在源码中找到),这里讲一下函数里面看到的几个点:

  • Observer 如果已经被添加到了 runloop,那么它就不能被添加到别的 runloop 里了。但是它还可以被添加到其它的 mode 当中
  • Observer 被添加到 mode 的 _observers 时会根据其 _order 进行排序,_order 数值越大在数组中的位置越靠后
  • Observer 被添加到一个 mode 后其 _rlCount + 1,并将其 _activities 同步到 mode 的 _observerMask 当中

每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。
当 runloop 在主函数中处于不同的时间点时,我们会通知注册了当前时间点的 Observer,对符合要求的 Observer 调用其回调函数。

需要注意的是,如果 Observer 不是重复的,那么当它执行一次回调后就会从 mode 中移除

CFRunLoopSourceRef

CFRunLoopSourceRef 的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct __CFRunLoopSource {
CFRuntimeBase _base;
_CFRecursiveMutex _lock;
/// 数值越低,优先级越高
CFIndex _order; /* immutable */
/// 仅对 sourece0 有效。它的值是激活时的时间戳,有值时表示此 sourece0 已准备好被执行
_Atomic uint64_t _signaledTime;
/// source retain run loops
CFMutableBagRef _runLoops;
union {
/// source0
CFRunLoopSourceContext version0; /* immutable, except invalidation */
/// source1
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};

可以看到 source1 与 source0 结构中的大部分成员变量都是相同的,除了最后的 _context 不同。
观察 CFRunLoopSourceContextCFRunLoopSourceContext1 的结构体之后,我们可以知道:

  • source1 相比于 source0 多了一个 mach port,这使得 source1 通过内核和其他线程相互发送消息,主动唤醒其它 runloop
  • source0 相比于 source1 多了两个函数指针 schedulecancel,分别在创建和销毁时调用,具体作用不知道,猜测是为了用来做一些初始操作

CFRunLoopSource 是添加到 runloop 中输入源的抽象表达。输入源通常会产生异步事件,例如网络端口上到达的消息或用户执行的操作。

输入源类型通常定义了一个用于创建和操作该类型对象的API,就像它是一个独立于 runloop 的实体一样。它提供一个函数来为对象创建一个CFRunLoopSource。CFRunLoopSource 可以被注册到 runloop,作为 runloop 和实际输入源类型对象之间的中介。输入源的例子包括CFMachPort、CFMessagePort和CFSocket。

CFRunLoopSource 有两类,Source0 由应用程序手动管理。当一个 Source0 准备好了被处理,应用程序的某些部分,也许是等待某个事件的线程上的代码,必须调用 CFRunLoopSourceSignal 来告诉 runloop Source0 已经准备好了。
CFSocket的 runloop Source目前是用 Source0 来实现的。

Source1 由 runloop 和内核管理。Source1 使用Mach端口来发出信号。当 Source1 的Mach端口上有消息到达时,内核会自动把 Source1 标记为激活状态。当 Source1 启动时,消息内容被交给 Source1 来处理。
CFMachPort和CFMessagePort的 runloop Source 目前是用 Source0 来实现的。

Source 可以同时注册多个 runloop 和 runloop mode。当 Source 发出信号时,无论哪一个 runloop 恰好先检测到信号,都会启动该 Source。将 Source 添加到多个线程的 runloop 中,可用于管理正在处理离散数据集的 “工作线程 “池,例如通过网络的客户端-服务器消息。当消息到达时, Source 得到信号,一个随机线程接收并处理请求。

我们可以使用 CFRunLoopAddSource() 函数将 Source 添加到 mode,实现太长了这里就不贴了,自己可以翻源码。这里简单的分析下

  1. 如果是 Source1,会预先测试该 mach port 是否能够发送消息,如果不能说明这个 Source1 有问题,直接崩溃
  2. 将 Source 依据类型分别添加到 rlm 的 _sources0 或者 _sources1 中。如果是 Source1 类型,则将其 mach port 添加到 rlm 的 _portSet,并以 key(mach port)/value(Source1) 的形式添加到 rlm 的 _portToV1SourceMap
  3. 将 runloop 添加到 Source 的 _runLoops

Source0 和 Source1 的处理函数分别是 __CFRunLoopDoSource0/__CFRunLoopDoSource1
没什么好说的,就是调用各自的回调函数。执行完毕后,Source 也不会被移除,所以你可以向 runloop 添加一个 source 来保证其线程一直存活

CFRunLoopTimerRef

CFRunLoopTimerRef 的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct __CFRunLoopTimer {
CFRuntimeBase _base;
/// 用 Bit 保存信息。Bit 0 表示 fireing 状态,Bit 1 表示是否在调出时被触发(不是很明白),Bit 2 表示是否处于销毁状态 Bit 3 表示是否有效
uint16_t _bits;
_CFRecursiveMutex _lock;
/// 包含此 timer 的 runloop
CFRunLoopRef _runLoop;
/// 包含此 timer 的 mode 集合
CFMutableSetRef _rlModes;
/// 下次触发的时间戳,单位秒
CFAbsoluteTime _nextFireDate;
/// 重复执行时的间隔。该值为 0 表示 timer 不重复
CFTimeInterval _interval; /* immutable */
/// 偏差,一般设定为间隔的 10% 比较好
CFTimeInterval _tolerance; /* mutable */
/// 下次触发时的 mach 时间戳
uint64_t _fireTSR; /* TSR units */
/// 优先级,数值越低表示优先级越高。貌似对 timer 没什么作用
CFIndex _order; /* immutable */
/// 触发时的回调
CFRunLoopTimerCallBack _callout; /* immutable */
/// 回调调用时需要的上下文
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

需要注意的是:

  • _nextFireDate 是下次触发时间相对于 2001-1-1 00:00:00 的时间戳,单位是秒
  • _fireTSR 下一次触发时间的 macg 时间戳,单位为纳秒
  • _order 貌似在 timer 没什么用,至少在 CF 源码内没发现其用途

在前面我们提到过,timer(NSTimer/CFRunLoopTimerRef) 的功能实际上是由 mode 操作实现的。
对于一个有宽余量 tolerance 的 timer 来说,它的触发时间是在 (fireDate) ~ (fireDate + tolerance) 之间的,我们把它最早的触发时间称为 SoftDeadline,最晚的触发时间称为 HardDeadline。
一个 mode 可能管理着多个 timer,它们被集中放在 _timers 中。其中的每个 timer 都有自己的 SoftDeadline 和 HardDeadline。由于 _timers 是按触发时间的早晚来排序的,所以,这所有 timer 中, SoftDeadline 最小的肯定是 _timers 数组中第一个 timer 的 SoftDeadline,我们将其赋值给 mode 的成员变量 _timerSoftDeadline。
但是 HardDeadline 最小的却不一定是 _timers 数组中第一个 timer 的 HardDeadline,因为这还需要比较 tolerance 的大小,总之,我们将 HardDeadline 的最小值赋值给 mode 的成员变量 _timerHardDeadline
mode 在获取到 _timerSoftDeadline 和 _timerHardDeadline 之后,就开始设置定时器,如果 _timerSoftDeadline 和 _timerSoftDeadline 相等,说明 tolerance 等于 =,此时 mode 使用 mk_timer 设置定时器; 否则 mode 使用 dispatch_source_t 设置定时器。
定时器触发,说明有 timer 需要执行回调了。这个时候我们在 _timers 数组中,判断哪些 timer 的 _fireTSR 时间小于等于当前时间,执行这些 timer 的回调,并重新计算出它们下一次的触发时间 fireTSR,然后重新计算出 mode 的 timerSoftDeadline 和 _timerHardDeadline,并开启下一个定时器。

想知道这些在源码中具体是怎么样的,就看接下来的分析吧~

我们可以使用 CFRunLoopTimerCreate() 来创建 CFRunLoopTimerRef 实例。
这个函数比较长而且简单这里就不贴了
函数里面值得注意的有下面几点:

  • 默认的 _tolerance 为 0,但实际上即使你设置为 0 还是不能保证会准时出发
  • 如果你设置的 fireDate 比现在要早,那么 _fireDate 会被赋值为 now,表示立刻触发。对于 repeat 的 timer 来讲,下一次触发时间还是会正常的

timer 创建后自然是要添加到 mode 中去了,CFRunLoopAddTimer() 帮助我们完成这个操作。
因为同上的理由这里就不贴代码了,主要就是在验证 timer 是否有效,最后如果有效的话则调用 __CFRepositionTimerInMode() 函数将 timer 添加到 mode 的 _timers 中

下面是 __CFRepositionTimerInMode() 函数的实现:

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
static void __CFRepositionTimerInMode(CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt, Boolean isInArray) {
if (!rlt) return;

CFMutableArrayRef timerArray = rlm->_timers;
if (!timerArray) return;
Boolean found = false;

// If we know in advance that the timer is not in the array (just being added now) then we can skip this search
/// 如果已经在数组中,则执行这一步
if (isInArray) {
CFIndex idx = CFArrayGetFirstIndexOfValue(timerArray, CFRangeMake(0, CFArrayGetCount(timerArray)), rlt);
if (kCFNotFound != idx) {
CFRetain(rlt);
CFArrayRemoveValueAtIndex(timerArray, idx);
found = true;
}
}
if (!found && isInArray) return;
/// 在数组中找到合适的位置
CFIndex newIdx = __CFRunLoopInsertionIndexInTimerArray(timerArray, rlt);
/// 在 timer 插入到上一步找到的位置中
CFArrayInsertValueAtIndex(timerArray, newIdx, rlt);
__CFArmNextTimerInMode(rlm, rlt->_runLoop);
if (isInArray) CFRelease(rlt);
}

函数主要做了下面几件事:

  • 如果该 timer 已经在数组中,则找到其位置并从数组中移除。然后在数组找到适合其插入的位置,找到后重新添加到数组中
  • 如果该 timer 不在数组中,则在数组找到适合其插入的位置,找到后将其添加到数组中
  • 以上两步将 timer 成功添加到数组后,调用 __CFArmNextTimerInMode() 重新计算 mode 的 _timerHardDeadline 和 _timerSoftDeadline

__CFArmNextTimerInMode() 的实现如下:

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
static void __CFArmNextTimerInMode(CFRunLoopModeRef rlm, CFRunLoopRef rl) {
/// min soft
uint64_t nextHardDeadline = UINT64_MAX;
/// min hard
uint64_t nextSoftDeadline = UINT64_MAX;

if (rlm->_timers) {
// Look at the list of timers. We will calculate two TSR values; the next soft and next hard deadline.
// The next soft deadline is the first time we can fire any timer. This is the fire date of the first timer in our sorted list of timers.
// The next hard deadline is the last time at which we can fire the timer before we've moved out of the allowable tolerance of the timers in our list.
for (CFIndex idx = 0, cnt = CFArrayGetCount(rlm->_timers); idx < cnt; idx++) {
CFRunLoopTimerRef t = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers , idx);
// discount timers currently firing
if (__CFRunLoopTimerIsFiring(t)) continue;

uint64_t oneTimerHardDeadline;
uint64_t oneTimerSoftDeadline = t->_fireTSR;
if (os_add_overflow(t->_fireTSR, __CFTimeIntervalToTSR(t->_tolerance), &oneTimerHardDeadline)) {
oneTimerHardDeadline = UINT64_MAX;
}

// We can stop searching if the soft deadline for this timer exceeds(超过 the current hard deadline. Otherwise, later timers with lower tolerance could still have earlier hard deadlines.
if (oneTimerSoftDeadline > nextHardDeadline) {
break;
}

if (oneTimerSoftDeadline < nextSoftDeadline) {
nextSoftDeadline = oneTimerSoftDeadline;
}

if (oneTimerHardDeadline < nextHardDeadline) {
nextHardDeadline = oneTimerHardDeadline;
}
}

/// 如果 _timerHardDeadline 和 _timerSoftDeadline 没变就不需要重新设置定时器了
if (nextSoftDeadline < UINT64_MAX && (nextHardDeadline != rlm->_timerHardDeadline || nextSoftDeadline != rlm->_timerSoftDeadline)) {
if (CFRUNLOOP_NEXT_TIMER_ARMED_ENABLED()) {
CFRUNLOOP_NEXT_TIMER_ARMED((unsigned long)(nextSoftDeadline - mach_absolute_time()));
}

cf_trace(KDEBUG_EVENT_CFRL_NEXT_TIMER_ARMED, rl, rlm, (nextSoftDeadline - mach_absolute_time()), 0);
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// We're going to hand off the range of allowable timer fire date to dispatch and let it fire when appropriate for the system.
/// 宽容度
uint64_t leeway = __CFTSRToNanoseconds(nextHardDeadline - nextSoftDeadline);
dispatch_time_t deadline = __CFTSRToDispatchTime(nextSoftDeadline);
if (leeway > 0) {/// 截止时间 - 预期时间 > 0
// Only use the dispatch timer if we have any leeway
// <rdar://problem/14447675>

// Cancel the mk timer
if (rlm->_mkTimerArmed && rlm->_timerPort) {
AbsoluteTime dummy;
mk_timer_cancel(rlm->_timerPort, &dummy);
rlm->_mkTimerArmed = false;
}

// Arm the dispatch timer
dispatch_source_set_timer(rlm->_timerSource, deadline, DISPATCH_TIME_FOREVER, leeway);
rlm->_dispatchTimerArmed = true;
} else {
// Cancel the dispatch timer
if (rlm->_dispatchTimerArmed) {
// Cancel the dispatch timer
dispatch_source_set_timer(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 888);
rlm->_dispatchTimerArmed = false;
}

// Arm the mk timer
if (rlm->_timerPort) {
mk_timer_arm(rlm->_timerPort, nextSoftDeadline);
rlm->_mkTimerArmed = true;
}
}
#else
if (rlm->_timerPort) {
mk_timer_arm(rlm->_timerPort, nextSoftDeadline);
}
#endif
} else if (nextSoftDeadline == UINT64_MAX) {
// Disarm the timers - there is no timer scheduled

if (rlm->_mkTimerArmed && rlm->_timerPort) {
AbsoluteTime dummy;
mk_timer_cancel(rlm->_timerPort, &dummy);
rlm->_mkTimerArmed = false;
}

#if USE_DISPATCH_SOURCE_FOR_TIMERS
if (rlm->_dispatchTimerArmed) {
dispatch_source_set_timer(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 333);
rlm->_dispatchTimerArmed = false;
}
#endif
}
}
rlm->_timerHardDeadline = nextHardDeadline;
rlm->_timerSoftDeadline = nextSoftDeadline;
}

函数里面值得注意的有下面几点:

  • _timerSoftDeadline 和 _timerSoftDeadline 取值都是 timer 中最小的 SoftDeadline 和 SoftDeadline,但是拥有最小 SoftDeadline 的 timer 的 SoftDeadline 却不一定是最小的,因为这还要考虑 _tolerance
  • 如果 _timerSoftDeadline 和 _timerSoftDeadline 和之前的值相同,说明不需要重新设置 mode 里面的定时器了
  • 如果 _timerSoftDeadline 和 _timerSoftDeadline 相同,则使用 mk_timer 设置定时器;否则使用 dispathch_source_t 设置定时器。我猜测是 mk_timer 精度更高,但是不能使用宽余量,所以此时用 dispatch_source_t 代替

添加完成后,自然是要看如何执行 timer 的,__CFRunLoopDoTimers() 函数帮我们完成该操作

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
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {	/* DOES CALLOUT */

cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_TIMERS | DBG_FUNC_START, rl, rlm, limitTSR, 0);

Boolean timerHandled = false;
CFMutableArrayRef timers = NULL;
for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);

if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
/// 将符合触发要求的 timer 取出来放在 timers 数组中
if (rlt->_fireTSR <= limitTSR) {
if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(timers, rlt);
}
}
}

CFRUNLOOP_ARP_BEGIN;

for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
timerHandled = timerHandled || did;
}
if (timers) CFRelease(timers);

CFRUNLOOP_ARP_END;


cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_TIMERS | DBG_FUNC_END, rl, rlm, limitTSR, 0);

return timerHandled;
}

方法给定了一个触发时间 limitTSR,遍历 _timers 并收集所有触发时间 _fireTSR 小于 limitTSR 的 timer
对所有符合触发要求的 timer 调用 __CFRunLoopDoTimer() 函数,看样子具体的执行过程是在这里

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

static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { /* DOES CALLOUT */

cf_trace(KDEBUG_EVENT_CFRL_TIMERS_FIRING | DBG_FUNC_START, rl, rlm, rlt, 0);

Boolean timerHandled = false;
uint64_t oldFireTSR = 0;

/* Fire a timer */
CFRetain(rlt);
__CFRunLoopTimerLock(rlt);

if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) { /// if1 start
void *context_info = NULL;
void (*context_release)(const void *) = NULL;
if (rlt->_context.retain) {
context_info = (void *)rlt->_context.retain(rlt->_context.info);
context_release = rlt->_context.release;
} else {
context_info = rlt->_context.info;
}
/// _interval = 0 表示 timer 触发过一次过就会被置为不可用状态
Boolean doInvalidate = (0.0 == rlt->_interval);
__CFRunLoopTimerSetFiring(rlt);
// Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
/// SoftDeadline 表示第一个 timer 第一次 fire 的时间,HardDeadline 表示最后一个 timer 第一次 fire 的时间
/// 重设这两个值,便于找到下一个未处理的 timer
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;
__CFRunLoopTimerUnlock(rlt);
__CFLock(&rl->_timerTSRLock);
oldFireTSR = rlt->_fireTSR;
__CFUnlock(&rl->_timerTSRLock);

__CFArmNextTimerInMode(rlm, rl);

__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);

CFRunLoopTimerCallBack callout = rlt->_callout;
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_TIMER | DBG_FUNC_START, callout, rlt, context_info, 0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(callout, rlt, context_info);
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_TIMER | DBG_FUNC_END, callout, rlt, context_info, 0);

CHECK_FOR_FORK();
if (doInvalidate) {
/// remove timer
CFRunLoopTimerInvalidate(rlt); /* DOES CALLOUT */
}
if (context_release) {
context_release(context_info);
}

__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopTimerLock(rlt);
timerHandled = true;
__CFRunLoopTimerUnsetFiring(rlt);
} /// if1 end
if (__CFIsValid(rlt) && timerHandled) { /// if2 start
/* This is just a little bit tricky: we want to support calling
* CFRunLoopTimerSetNextFireDate() from within the callout and
* honor that new time here if it is a later date, otherwise
* it is completely ignored. */
if (oldFireTSR < rlt->_fireTSR) { /// if3 start
/* Next fire TSR was set, and set to a date after the previous
* fire date, so we honor it. */
__CFRunLoopTimerUnlock(rlt);
// The timer was adjusted and repositioned, during the
// callout, but if it was still the min timer, it was
// skipped because it was firing. Need to redo the
// min timer calculation in case rlt should now be that
// timer instead of whatever was chosen.
__CFArmNextTimerInMode(rlm, rl);
} else {
uint64_t nextFireTSR = 0LL;
uint64_t intervalTSR = 0LL;
if (rlt->_interval <= 0.0) {
} else if (TIMER_INTERVAL_LIMIT < rlt->_interval) {
intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
} else {
intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
}

if (LLONG_MAX - intervalTSR <= oldFireTSR) {
nextFireTSR = LLONG_MAX;
} else {
if (intervalTSR == 0) {
// 15304159: Make sure we don't accidentally loop forever here
CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
HALT;
}
uint64_t currentTSR = mach_absolute_time();
nextFireTSR = oldFireTSR;
while (nextFireTSR <= currentTSR) {
nextFireTSR += intervalTSR;
}
}
CFRunLoopRef rlt_rl = rlt->_runLoop;
if (rlt_rl) { /// if3 start
CFRetain(rlt_rl);
CFIndex cnt = CFSetGetCount(rlt->_rlModes);
STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
CFSetGetValues(rlt->_rlModes, (const void **)modes);
// To avoid A->B, B->A lock ordering issues when coming up
// towards the run loop from a source, the timer has to be
// unlocked, which means we have to protect from object
// invalidation, although that's somewhat expensive.
for (CFIndex idx = 0; idx < cnt; idx++) {
CFRetain(modes[idx]);
}
__CFRunLoopTimerUnlock(rlt);
for (CFIndex idx = 0; idx < cnt; idx++) {
CFStringRef name = (CFStringRef)modes[idx];
modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
CFRelease(name);
}
__CFLock(&rl->_timerTSRLock);
rlt->_fireTSR = nextFireTSR;
rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
for (CFIndex idx = 0; idx < cnt; idx++) {
CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
if (rlm) {
__CFRepositionTimerInMode(rlm, rlt, true);
}
}
__CFUnlock(&rl->_timerTSRLock);
for (CFIndex idx = 0; idx < cnt; idx++) {
__CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
}
CFRelease(rlt_rl);
} else {
__CFRunLoopTimerUnlock(rlt);
__CFLock(&rl->_timerTSRLock);
rlt->_fireTSR = nextFireTSR;
rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
__CFUnlock(&rl->_timerTSRLock);
}
} /// if3 end
} else {
__CFRunLoopTimerUnlock(rlt);
} /// if2 end
CFRelease(rlt);

cf_trace(KDEBUG_EVENT_CFRL_TIMERS_FIRING | DBG_FUNC_END, rl, rlm, rlt, 0);

return timerHandled;
}

这个函数比较长,但是也比较重要

  • 再判断一次 timer 是否满足
  • 设置 timer 为触发状态,并设置 mode 的 _timerSoftDeadline,_timerHardDeadline 为 UINT64_MAX,调用 __CFArmNextTimerInMode() 在 mode 开启下一个定时器(因为之前设置的已经触发了)
  • 执行 timer 的回调,结束之后如果 timer 不重复则将其从 mode 中移除,否则将 timer 设置为未触发状态
  • 接下来重新调用一次 __CFArmNextTimerInMode(),因为上一次调用 __CFArmNextTimerInMode,这个 timer 处于触发状态所以不能参与,现在是未触发状态就可以参与了

至此,CFRunLoopTimerRef 的分析完毕

dispatch_source_t 使用

1
2
3
4
5
6
7
8
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, 0), 3 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(source, ^{
//定时器触发时执行
NSLog(@"timer响应了");
});
//启动timer
dispatch_resume(source);

在 runloop 中,给 dispatch_source_t 的触发时间设置为 DISPATCH_TIME_FOREVER 达到取消定时器触发的目的

需要注意的是,dispatch_source_set_timer() 函数中,如果触发时间是 DISPATCH_TIME_NOW 或者 dispatch_time 类型的,那么定时器将对照 mach 时间来触发; 否则,将对照 gettimeofday(3) 来触发

mk_timer 使用

MK_TIMER的原理很简单,其核心就是:

  • mk_timer_arm(mach_port_t, expire_time) 在 expire_time 的时候给指定了 mach_port 的发送消息
  • mk_timer_cancel(mach_port_t, &result_time) 取消 mk_timer_arm 注册的消息

Runloop 主函数

这里我也写一个 runloop 的内部逻辑,可以跳过先看后面的分析

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
/**
@param seconds:超时时间
@param stopAfterHandle:处理完 source 即退出,默认为 false
@param previousMode:默认为 nil,如果不为空,则表示 __CFRunLoopRun 还未退出就又调用了一次 __CFRunLoopRun
*/
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

// dispatchPort 表示 dispatch_main_queue port,可能为空
__CFPort dispatchPort = ...;
// modeQueuePort 表示 dispatch_rlm_queue port,可能为空
mach_port_name_t modeQueuePort = ...;

/// 当超时时间大于 0 且小于某个值,则用 dispatch_source_t 开启一个定时器
if (seconds > 0 && seconds < DISPATCH_TIME_FOREVER) {
dispatch_source_t timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_resume(timeout_timer);
}

Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;

do {
/// 可能会接收到 mach 消息 的 mach port 集合
__CFPortSet waitSet = rlm->_portSet;

/// 通知 Observers: RunLoop 即将处理 Timer
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

/// 通知 Observers: RunLoop 即将处理 Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

/// 执行被加入的 block
__CFRunLoopDoBlocks(rl, rlm);

/// 处理 Source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
/// 执行完 source0 的回调函数,可能会向 RunLoop 加入了一些新的 block,所以在这里执行它们
__CFRunLoopDoBlocks(rl, rlm);
}

/// poll 表示本次 loop 不会进入休眠状态,即不会休眠等待 mach msg
Boolean poll = ...;

/// 通知 Observers: RunLoop 即将休眠
if (!poll) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
/// 设置 runloop 为休眠状态
__CFRunLoopSetSleeping(rl);


do {
/**
休眠接受 mach msg,有以下几种情况会被唤醒
1. 定时器触发唤醒
2. source1 事件
3. 调用 CFRunLoopWakeUp() 唤醒
4. 超时唤醒
*/
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort);

if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
/// 定时器触发唤醒
if (rlm->_timerFired) {
rlm->_timerFired = false;
break;
}
} else {
/// 其它情况唤醒
break;
}
} while (1)

/// 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

handle_msg:;

if (MACH_PORT_NULL == livePort) {
/// 被其它原因唤醒,例如 poll = true
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
/// 被 CFRunLoopWakeUp 唤醒
// handle nothing
} else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
/// 被 dispatch_source_t 类型的定时器唤醒
/// 处理 timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
} else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
//// 被 mk_timer 类型的定时器唤醒
/// 处理 timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
} else if (livePort == dispatchPort) {
/// 被 main_dispatch_queue port 发送的消息
/// 处理提交到主队列的任务
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
} else {
/// 处理 source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
if (NULL != reply) {
/// source1 可以向别的 port 发送消息
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
}
}

if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
} while (ret)

return ret;
}

初始化 mach port

初始化 dispatchPort 和 modeQueuePort 两个端口
dispatchPort 表示 dispatch_main_queue port(主队列的 mach port),可能为空
modeQueuePort 表示 dispatch_rlm_queue port(当前 mode 队列的 mach port),这个 port 是用来处理 timer 的,可能为空

超时定时器

当超时时间 second 大于 0 且小于某个值时,初始化一个dispatch_source_t 类型的定时器,并开启,用来处理 runloop 超时的情况。如果超时,则调用函数 CFRunLoopWakeUp() 唤醒 rl

接着进入了一个内部循环 do {} while () 中

处理 source0

在处理 source0 之前我们先调用 __CFRunLoopDoBlocks() 函数处理加入的 block。
处理完 source0 之后立马又调用了一次 __CFRunLoopDoBlocks(),我裂解为在执行 source0 的回调函数中,有可能像 rlm 又添加了 block,所以要处理它们。

这里我有个猜测,使用 dispatch_async(dispatch_queue_t queue, dispatch_block_t block) 添加的 block 其实是添加到 rlm 里面的,只不过因为对 GCD 不熟悉现在还无法下结论。
但使用代码调试的时候可以看到 block 是在 __CFRunLoopDoBlocks 里面被调用的。

这里分为两部分,首先声明了一个布尔值 poll,它表示 runloop 在此次 loop 中不会进入休眠等待 mach msg 的状态,处理完任务就退出。
它的成立条件可以是下面条件中的一种

  • 处理了 source0
  • 超时时间为 0

source0 并不能主动唤醒 runloop,所以处理 source0 之前必须要用 CFRunLoopWakeUp() 唤醒 runloop。
这也就能理解 poll 的作用了,人家唤醒你来处理任务,肯定是希望你赶紧处理完然后退出的,怎么还能让你浪费时间休眠接受 mach msg 呢

后面应该不可能调到红色框内,因为前面 didDispatchPortLastTime 初始化为了 true。
我猜测这是因为 swift 中这部分的实现跟 oc 中的实现不完全一样。总之这里就不管了了

runloop 进入休眠状态,调用 __CFRunLoopServiceMachPort() 接受 mach msg,接受到消息后被唤醒。

此时如果接收到消息的 port 不是 modeQueuePort(rlm_queue_port),则继续走下。
如果是的话,则调用 _dispatch_runloop_root_queue_perform_4CF() 函数。

在创建 rlm 时我们创建一个 dispatch_queue_t,赋值给了 _queue。创建了一个 dispatch_source_t 定时器,并将其回调 handler block 提交给了 _queue。当定时器被触发时,发送消息给 modeQueuePort,接受到消息后,我们需要调用 _dispatch_runloop_root_queue_perform_4CF() 函数处理之前提交过的 handler block,而这个回调在初始化 rlm 就已经写死了:将 rlm->_timerFired 赋值为 true。

所以接下来有个判断 rlm->_timerFired 是否为 true,如果为 true 即定时器触发了,所以跳出循环去处理 timer 的回调。如果不是我就不清楚是什么情况了

接下来就是 runloop 处理被唤醒的原因了

此种情况是因为在接受消息时因为各种原因没有正确的接收到消息而退出休眠的状态,例如设置超时时间为 0,mach_msg 还没接收到消息就退出接受状态了


runloop 被调用 CFRunLoopWakeUp 函数唤醒

被 rlm->_timerSource 定时器唤醒。

此时去执行可用的 timer 回调。
如果不存在符合要求的 timer,说明定时器触发早了了,说明我们设置的触发时间有问题。调用 __CFArmNextTimerInMode() 设置下一个触发的时间点

被 mk_timer 唤醒

此时去执行可用的 timer 回调。
在 windows 平台 mk_timer 定时器可能提前触发,所以我们需要重新开启 mk_timer 定时器,除了调用 __CFArmNextTimerInMode 函数,还需要先将 rlm 的_timerSoftDeadline 和 _timerHardDeadline 改变才行。

被 dispatch_main_queue 唤醒
调用 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE 处理提交到 dispatch_main_queue 的任务

被 source1 唤醒

调用 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION 处理 source1。
因为 source1 自己拥有一个 mach port,在 source1 的回调函数中可能会往别的 port 发送消息


执行了上述可能的各种回调,可能又有 block 提交到了当前 runloop 当中,所以我们这里再调用一次 __CFRunLoopDoBlocks()

最后,根据前面的处理结果来判断是否退出 loop

用 RunLoop 实现的功能

这一部分我觉得 https://blog.ibireme.com/2015/05/18/runloop/ 这一篇文章里面已经写的很好了,我这里就不赘述了。

参考

深入理解RunLoop
https://zhuanlan.zhihu.com/p/63184073

作者

千行

发布于

2020-05-24

更新于

2022-10-21

许可协议

评论