iOS源码解析: runloop的运行原理

这部分主要是runloop的源码解析

下边的这张图可以说是讲解runloop最为经典的了,来自于 深入理解RunLoop

runloop-yy

接下来,会详细解读源码的一些细节,有些部分的代码会比较长,粘贴在这里也是为了保持其完整。

入口:CFRunLoopRun

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CFRunLoopRun(void) {	/* DOES CALLOUT */
int32_t result;
do {
// 用DefaultMode启动。
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 用指定的mode启动,允许设置超时时间
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

CFRunLoopRunSpecific函数通过第二个参数CFStringRef modeName来指定runloop运行的Mode。这里,使用的modeName参数是一个字符串,而非封装好的RunLoopMode对象。参数seconds即是外部调用时传入的runloop运行时间,决定了runloop能够运行多久,外部传入的,内部使用gcd timer或mk timer来实现。

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
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
/// 首先根据modeName找到对应的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 若mode是empty的,即没有Mode Item(source、timer、observer),则返回。
/// 所以runloop要想存活,必须有mode item存在,或者有block要执行。
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}

/// _per_run_data是runloop每一次run的时候的相关数据。之前已有详细介绍。
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;

/// 先将kCFRunLoopEntry状态通知给observer
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 正式进入runloop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
/// runloop退出,将kCFRunLoopExit状态通知给observer
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}

__CFRunLoopFindMode

__CFRunLoopFindMode函数比较长,但是目的很明确:根据runloop对象和modeName查找runloopMode对象,若找不到则新创建一个。

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
/* call with rl locked, returns mode locked */
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
CHECK_FOR_FORK();
CFRunLoopModeRef rlm;
struct __CFRunLoopMode srlm;
// 将srlm指针指向的内存区域的特定长度,设置为0
memset(&srlm, 0, sizeof(srlm));
_CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);
srlm._name = modeName;
// 从runloop对象的_modes容器中,查询mode对象
rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
if (NULL != rlm) {
__CFRunLoopModeLock(rlm);
return rlm;
}
if (!create) {
return NULL;
}
// 如果没有该mode对象,则新创建一个。
rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
if (NULL == rlm) {
return NULL;
}
__CFRunLoopLockInit(&rlm->_lock);
rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
rlm->_stopped = false;
rlm->_portToV1SourceMap = NULL;
rlm->_sources0 = NULL;
rlm->_sources1 = NULL;
rlm->_observers = NULL;
rlm->_timers = NULL;
rlm->_observerMask = 0;
rlm->_portSet = __CFPortSetAllocate();
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;

kern_return_t ret = KERN_SUCCESS;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
rlm->_timerFired = false;
// 这个queue,后续有用到?
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_runloop_timer_4CF(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
#if USE_MK_TIMER_TOO
// 这个_timerPort是干啥的?
rlm->_timerPort = mk_timer_create();
ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif
// runloop对象的_wakeUpPort会用到哪里?
ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);

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

__CFRunLoopModeIsEmpty

__CFRunLoopModeIsEmpty函数接收runloop对象和runloopMode参数,用于判断RunloopMode是否有对应的Source(也可理解为runloopMode Item、block)。即判断该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
// expects rl and rlm locked
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
CHECK_FOR_FORK();
if (NULL == rlm) return true;
#if DEPLOYMENT_TARGET_WINDOWS
if (0 != rlm->_msgQMask) return false;
#endif

/// 主线程会比较特殊,其mode item肯定不会为空
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) return false; // represents the libdispatch main queue

/// 判断source0、source1、timer
if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;

/// 判断block,依次执行runloop中的block任务。
struct _block_item *item = rl->_blocks_head;
while (item) {
struct _block_item *curr = item;
item = item->_next;
Boolean doit = false;
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
} else {
doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
}
if (doit) return false;
}
return true;
}

该函数判断是否有block任务的一句代码比较值得注意:

1
doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));

条件1(当前runloopMode的名字与block中存储的一致)、条件2(block任务指定于CommonModes中执行,且当前runloopMode的名字在commonModes中)满足其一即可认为该block任务需要执行。

runloop支持嵌套使用

注意下边这段简化的流程:

1
2
3
4
5
6
7
8
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;

/// 运行runloop,从Entry至Exit的所有流程

__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;

这段代码说明了以下三点:

  1. runloop运行之前,会有previous数据(previousPerRun)和mode(previousMode)的保存操作
  2. runloop运行完毕之后,会有previous数据(previousPerRun)和mode(previousMode)的恢复操作
  3. runloop支持嵌套使用:在runloop的运行过程中(如正在处理任务的callout函数)再次运行,则当前runloop的数据和状态被保存,之后再恢复。

而关于上边的第三点,苹果的官方文档也写得很清楚:

Run loops can be run recursively. You can call CFRunLoopRun() or CFRunLoopRunInMode(::_:) from within any run loop callout and create nested run loop activations on the current thread’s call stack. You are not restricted in which modes you can run from within a callout. You can create another run loop activation running in any available run loop mode, including any modes already running higher in the call stack.

那么问题来了,runloop的恢复操作仅仅给perRunData和runloopMode恢复值既可以做到了么???

小结

总结一下CFRunLoopRunSpecific()函数的所做的任务如下:

  1. __CFRunLoopFindMode找到对应的runloopMode
  2. __CFRunLoopModeIsEmpty检查source是否为空
  3. 对runloop做PerRunData和runloopMode的保存操作
  4. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
  5. __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
  6. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  7. 对runloop做PerRunData和runloopMode的恢复操作

__CFRunLoopDoObservers函数将kCFRunLoopEntry这个状态,同步给各个Observer去执行各自的回调函数。

1
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

接下来开始研究真正的runloop运行入口:__CFRunLoopRun。

真正的入口:__CFRunLoopRun

__CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); 函数接收了不少参数。

其函数原型如下:

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

这个函数可以说是非常非常的长了。。。不过这就是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
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();

if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}

mach_port_name_t dispatchPort = MACH_PORT_NULL;
/// 判断是主线程队列main_queue。
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
/// 这里的 dispatchPort 是什么?
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();

#if USE_DISPATCH_SOURCE_FOR_TIMERS
/// 是否使用了GCD timer?
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

/// 这段代码表明,runloop设置的时间间隔,其实也是使用的GCD timer来实现的。dispatch_source_t与runloop source是否有关???
/// GCD timer是依赖于内核的,所以非常精准。不受runloop影响。
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
/// 将timeout_context关联到timer中
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
/// __CFRunLoopTimeout是一个函数指针,内部会调用CFRunLoopWakeUp(context->rl);用于唤醒runloop
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}

Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
__CFPortSet waitSet = rlm->_portSet;

__CFRunLoopUnsetIgnoreWakeUps(rl);

/// 注意:runloop即将开始做事,处理timer、source、block等。
/// 将kCFRunLoopBeforeTimers状态通知给observer
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
/// 将kCFRunLoopBeforeSources状态通知给observer
/// 即将触发source0回调,非port
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

/// 执行加入到runloop中的block
__CFRunLoopDoBlocks(rl, rlm);

// 处理Sources0事件,那Timer事件在哪里处理的???在runloop唤醒之后,为何要将timer放到runloop被唤醒之后呢?
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
/// 这里为啥又有一个__CFRunLoopDoBlocks
__CFRunLoopDoBlocks(rl, rlm);
}

/*
每次 loop 如果处理了 source0 任务,那么 poll 值会为 true,直接的影响是不会 DoObservers-BeforeWaiting 和 DoObservers-AfterWaiting,也就是说 runloop 会直接进入睡眠,而且不会告知 BeforeWaiting 和 AfterWaiting 这两个 activity。所以你看,有些情况下,可能 runloop 经过了几个 loop,但你注册的 observer 却不会收到 callback。
*/
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;
/// 有mach port即为source1事件,对应一个mach msg,处理该source1事件,即该mach msg。
/// 如用户操作、网络等都会通过port。
/// 为何这里是dispatchPort?
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}

/// 以上代码就是runloop运行做的事情,这之后就是runloop即将进入休眠状态了。
/// 而如果有使用port,则通过handle_msg跳转到__CFRunLoopSetIgnoreWakeUps,即runloop唤醒

didDispatchPortLastTime = false;

/// 注意:runloop的事情已经做完,即将进入休眠
/// 休眠,即执行do-while(1)操作,在其中执行mach_msg_trap()切换到内核态。之后,则只能通过source1主动唤醒runloop。
/// 用户触摸事件,会包装成一个source1事件,通过port,mach_msg传递给Mach底层,然后调用trap()函数完成OS状态切换。
/// 将kCFRunLoopBeforeWaiting状态通知给observer
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
/// runloop开始休眠,即后边的do-while(1)循环。
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)

// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.

__CFPortSetInsert(dispatchPort, waitSet);

__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);

CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
/// 这里的do-while(1)仅是循环,那实际上runloop如何做到休眠???
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
/*
这个__CFRunLoopServiceMachPort函数很关键。调用mach_msg,等待接收mach port的msg。
线程即将进入休眠,直到被如下事件之一唤醒:
1,一个基于port的source1事件
2,一个timer的触发时间
3,runloop自身的超时时间到了
4,手动唤醒,即使用WakeUp函数调用。

即该函数执行后,runloop即休眠、线程休眠,则这行代码之后的都不会执行的。直到runloop被唤醒,即线程被唤醒!!!这一点很重要。
这里的waitSet,即函数第一个参数说明了什么?进入休眠,但是可以接收waitSet端口的mach msg。
*/
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

/// 上边runloop已经进入休眠了,那如何唤醒,如何结束while(1)???
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
/// 这个while循环又是做什么???以及里边的break有啥作业,没看明白?
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
/// 调用mach_msg,等待接收mach port的msg。线程休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif


#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif

/// 一旦退出了上边的do-while(1),则runloop被唤醒了
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
/// 这里有统计runloop的休眠时间,这个时间是否可以用来做一些什么事情呢?
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.

__CFPortSetRemove(dispatchPort, waitSet);

__CFRunLoopSetIgnoreWakeUps(rl);

// __CFRunLoopSetSleeping(rl);与__CFRunLoopUnsetSleeping(rl);一一对应。
// runloop的休眠与唤醒
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);

/// 至于线程是如何被唤醒的,则不用管。内核自行处理的。
/// 将kCFRunLoopAfterWaiting同步给observer:刚从休眠状态中唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

/// 唤醒后,收到消息即处理消息;即通过port唤醒runloop,可以直接goto语句到这里。
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);

if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
/// runloop被唤醒之后,继续做事情。为何不与上边的代码复用?
/// 因为timer被唤醒
CFRUNLOOP_WAKEUP_FOR_TIMER();
/// 触发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) {
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) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
/// 这里的dispatchPort是否意味着就是主线程?是的!
/// 如果有dispatch到main queue的block,即执行。
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
/// 注册一个回调
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
/// 这里的SOURCE是source0还是source1??source1,基于port
/// 能主动唤醒
CFRUNLOOP_WAKEUP_FOR_SOURCE();

// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
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) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
/// 处理source1事件
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}

// 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

/// 执行加入到runloop的block。
__CFRunLoopDoBlocks(rl, rlm);


if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入runloop时,参数说处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
/// runloop超时了
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)) {
/// mode item一个都没有了
retVal = kCFRunLoopRunFinished;
}

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
/// retVal即为runloop处理完了所有事件,又继续进入一次循环,do-while(1)。
} while (0 == retVal);

if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}

return retVal;
}

这个函数看起来确实比较累,抓住几个核心的点,再梳理起来就比较容易。

外层循环do-while(0 == retVal)

外层循环即为runloop的一次loop的完整流程。

retVal的值一旦非零,这个外层do-while循环则会退出,__CFRunLoopRun函数返回一个非零值,也意味着runloop的运行结束。而retVal变成非零的情况只有以下几种:

1
2
3
4
5
6
7
/* Reasons for CFRunLoopRunInMode() to Return */
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
kCFRunLoopRunFinished = 1,
kCFRunLoopRunStopped = 2,
kCFRunLoopRunTimedOut = 3,
kCFRunLoopRunHandledSource = 4
};

仔细看下边这段源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入runloop时,参数说处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// runloop超时了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
// 被外部调用者强行停止了
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
// 被外部调用者强行停止了
// 这两者有啥区别?
// __CFRunLoopIsStopped 中 return (rl->_perRunData->stopped) ? true : false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// mode item一个都没有了,正常结束
retVal = kCFRunLoopRunFinished;
}

结论已经很明显了:

  1. stopAfterHandle是__CFRunLoopRun函数的第四个参数,也是CFRunLoopRunSpecific函数的第四个参数returnAfterSourceHandled。CFRunLoopRun函数调用时,该参数为false,所以NSRunLoop的run:一旦执行则无法停止。而CFRunLoopRunInMode函数调用时,该参数是在调用NSRunLoop的runUntilDate:或runMode:beforeDate:时进行相应判断进而设置的。所以符合条件的话,本次loop的任务执行完毕(sourceHandledThisLoop)后就退出。
  2. runloop超时是通过timeout_context来进行判断的。
  3. __CFRunLoopIsStopped和rlm->_stopped分别是啥???是外部强行停止?暂时不清楚如何停止???
  4. 再次调用__CFRunLoopModeIsEmpty判断runloopMode的source是否为空,没有则结束runloop。

内层循环do-while(1)

这个内层的do-while(1)循环,就是runloop进入休眠状态。 做的事情很明确,就是调用__CFRunLoopServiceMachPort函数。

会执行以下代码进入休眠状态(内核态)。一旦这个内层的do-while(1)退出循环,即为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
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;

__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

// 上边runloop已经进入休眠了,那如何唤醒,如何结束while(1)???
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// 这个while循环又是做什么???以及里边的break有啥作业,没看明白?
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);

这个__CFRunLoopServiceMachPort函数很关键。调用mach_msg,等待接收mach port的msg。线程即将进入休眠,直到被如下事件之一唤醒:

  1. 一个基于port的source1事件
  2. 一个timer的触发时间
  3. runloop自身的超时时间到了
  4. 手动唤醒。

runloop进入休眠

__CFRunLoopServiceMachPort即是mach msg发挥作用的地方了。其中会执行mach_msg函数,使得系统从用户态切换至内核态。且mach_msg的调用代码让人记忆相当深刻。。。

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
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 ... */
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
// msg的port方向,若从_dispatch_get_main_queue_port_4CF到MACH_PORT_NULL,则休眠???
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }

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);

// Take care of all voucher-related work right after mach_msg.
// If we don't release the previous voucher we're going to leak it.
voucher_mach_msg_revert(*voucherState);

// Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
*voucherState = voucher_mach_msg_adopt(msg);

if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
// Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
// CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
*voucherCopy = voucher_copy();
} else {
*voucherCopy = NULL;
}
}

CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}

这段代码里边有个for循环,其中不断构建mach_msg对象,执行mach_msg()函数。之前有讲到mach_msg()内部实际上是调用了mach_msg_trap()系统调用,所以,这个函数里边会触发操作系统状态的改变,即从用户态切换至内核态,runloop进入休眠。

1
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);

如果有msg要处理,跳转到handle_msg。这一步对应着就是有Source1要处理。

1
2
3
4
5
6
7
// 首次didDispatchPortLastTime为true,不会执行
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
}

关于休眠,要注意的是,runloop休眠后,线程也进入休眠,CPU不会做任何事情了,之后的代码什么的都不会执行。而一旦使用mach_msg()函数给mach内核发送了休眠的消息,系统进入了内核态,之后我们能做的只有等待而已。静静地等待runloop被唤醒而已

runloop被唤醒

退出内层循环,即为runloop被唤醒了。有两种情况???

modeQueuePort和livePort判断,或者rlm->_timerFired = false; 为啥???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 上边runloop已经进入休眠了,那如何唤醒,如何结束while(1)???
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// 这个while循环又是做什么???以及里边的break有啥作业,没看明白?
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}

唤醒条件:

  1. 给于port的source1事件
  2. Timer时间到了
  3. runloop超时???
  4. 被调用者唤醒?

小结

知道了 内层do-while(1)是系统触发状态切换而进入休眠状态 这一个关键点之后,则该内层循环之前和之后代码各自的作用,根据runloop的运行流程图就很容易理解了。

  1. 内层循环do-while(1)之前:处理Timer、Source、Block任务,之后runloop即进入休眠状态
  2. 内层循环do-while(1)之后:runloop被唤醒,处理一些任务(这里就是Source1的任务了)

内层循环do-while(1)之前

这一部分是runloop执行任务的地方,也是runloop进入休眠之前执行的任务,流程大概如下:

  1. 设置runloop的状态
  2. 将BeforeTimers和BeforeSources两个状态通知到Observer
  3. 执行block任务
  4. 执行Sources0事件
  5. 将BeforeWaiting状态通知到Observer
  6. 设置runloop的状态

关于这个阶段,有一个疑问:发送了BeforeTimers状态给Observer,但是没有执行Timer事件,这是为什么?

实际上,Timer事件是在runloop先进入休眠、再被唤醒后执行的,即在内层循环do-while(1)之后阶段中。我们可以从__CFRunLoopDoTimers函数的调用时机看出来。

关于runloop执行任务的更详细解读,会在后续章节 真正做事情的地方 __CFRunLoopDoXXX 中体现。

内层循环do-while(1)之后

这个阶段是runloop被唤醒之后执行任务的阶段。

此时,runloop被唤醒,会先统计当次runloop的休眠时间(_sleepTime字段):rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));。然后设置唤醒后的一些状态(CFRunLoopSetIgnoreWakeUps、CFRunLoopUnsetSleeping)。将AfterWaiting状态通知给Observer:

1
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

接下来,就是runloop被唤醒后,处理任务的地方了。

  1. 判断livePort和modeQueuePort
  2. 判断_timerPort,执行timer回调
  3. 判断dispatchPort
  4. 以上都不是,那么就是Source1事件了。先根据runloop、runloopMode和livePort查找对应的Source1,然后调用__CFRunLoopDoSource1函数来执行任务。如果有回复的mach msg,会再通过mach_msg函数将回复消息发送到指定mach port。这就是mach核心的线程通信方式。
  5. 最后一步,再次执行block任务。然后,继续外层do-while循环,继续执行下一次loop。

真正做事情的地方 __CFRunLoopDoXXX

runloop源码中会有一系列 __CFRunLoopDo 开头的函数:

1
2
3
4
5
__CFRunLoopDoBlocks(rl, rlm);
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

这些函数是runloop执行任务的关键,后边会详细介绍。注意,每个函数接收的参数都有runloop对象和runloopMode对象,这也验证了runloopMode之间相互隔离,各自处理各自任务的基本事实。

下边对于各个函数的讲解顺序与其实际执行顺序无关。

处理Block任务 __CFRunLoopDoBlocks

CFRunLoopDoBlocks(rl, rlm)会执行加到runloop中的block任务,对_block_item的链表结构进行遍历,触发 ***CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);*** 来执行block。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16