iOS Runloop 探索

RunLoop

实际上是一个对象,这个对象管理了其需要处理的事件和消息.

macOS/iOS系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。

CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的(CFSpinlock/CFSpinUnlock)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};

typedef struct __CFRunLoop * CFRunLoopRef;

NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

CFRunLoopRef 的代码是开源的,你可以在这里 下载到整个 CoreFoundation 的源码来查看。

RunLoop与线程的关系

CFRunLoop 是基于 pthread 来管理的,苹果不允许直接创建RunLoop,可以通过CFRunLoopGetMain()和CFRunLoopGetCurrent()获取。

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

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&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);
__CFSpinLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&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
__CFSpinUnlock(&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;
}

你可以通过pthread_main_thread_np()或[NSThread mainThread]来获取主线程;也可以通过 pthread_self()或[NSThread currentThread]来获取当前线程。

线程和RunLoop之间是一一对应的,其关系是保存在一个全局的Dictionary(pthread_t : CFRunLoopRef)里的同时,也在线程里保存了一份
_pthread_setspecific_direct(CF_TSD_KEY, arg) 可通过本线各的
_CFGetTSD(CFTSDKeyRunLoop) // CFTSDKeyRunLoop 10
获取已保存的RunLoop。

线程刚创建时并没有RunLoop,如果你不主动获取,那它一直都不会有。RunLoop的创建是发生在第一次获取时,RunLoop的销毁是发生在线程结束时。你只能在一个线程的内部获取其RunLoop(主线程除外)。

1
2
3
4
5
6
7
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

typedef struct __CFRunLoopTimer * CFRunLoopTimerRef;

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

CFRunLoopSourceRef

1
2
3
4
5
6
7
8
9
10
11
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};

Source有两个版本:Source0 和 Source1。

  • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。

CFRunLoopObserverRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};

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

包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

CFRunLoopTimerRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,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
truct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};

苹果提供的 Mode 有五个,其中两个是公开的:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 kCFRunLoopCommonModes (NSRunLoopCommonModes)

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
void CFRunLoopRun(void) {	/* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */

/* RunLoop 正在被移除 */

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;
}
...

/* 通知 Observers: RunLoop 进入 loop */

if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

/* 通知 Observers: RunLoop 离开 loop */

if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
...
return result;
}

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;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();

#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

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 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
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);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}

Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
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);

/* 通知 Observers: RunLoop 将触发 Timer 回调 */

if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

/* 通知 Observers: RunLoop 将触发 Source0 (非port) 回调 */

if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

/* RunLoop 执行被加入的block */

__CFRunLoopDoBlocks(rl, rlm);

/* RunLoop 处理 Source0 (非port) 回调 */

Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}

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;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}

didDispatchPortLastTime = false;

/* 通知 Observers: RunLoop 的线程即将进入休眠 */

if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__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);

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
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);

if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}

/* RunLoop 调用 mach_msg 阻塞等待接受 mach_port 的消息 */

msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
...
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);

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

// user callouts now OK again

/* 通知 Observers: RunLoop 的线程刚刚被唤醒了 */

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

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 DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {

/* Timer 到了,触发这个Timer的回调 */

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

/* 若有main dispatch,执行回调 */

CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
...

__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {

/* Source1 (基于port) 发出事件了,处理这个事件 */

CFRUNLOOP_WAKEUP_FOR_SOURCE();
// 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;
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);
}
...
#endif
}
}
#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);

/* 处理完事件或超时或被强制停止 将退出循环 */

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;
}
} while (0 == retVal);

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

return retVal;
}

OSX和iOS架构

RunLoop的Source0负责处理App内部事件,而Souces1则接收系统发来的Mach Message再转发给Souces0处理。

系统注册了一个基于port 的source ,回调函数为__IOHIDEventSystemClientQueueCallback。通过测试,无论你点击屏幕,甚至是你晃动手机,都是触发这个回调。

经查资料知道这首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。

_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。

以下是点击Button后的线程堆栈

App 启动后 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
<CFRunLoop 0x6000001f9300 [0x10306ec80]>{wakeup port = 0x2203, stopped = false, ignoreWakeUps = false, 
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600000243ae0 [0x10306ec80]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x10713ae88 [0x10306ec80]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x103044818 [0x10306ec80]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x600000244050 [0x10306ec80]>{type = mutable set, count = 13,
entries =>
0 : <CFRunLoopSource 0x600000176740 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10fe7a75a)}}
1 : <CFRunLoopObserver 0x604000139c80 [0x10306ec80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10b6774ce), context = <CFRunLoopObserver context 0x0>}
4 : <CFRunLoopSource 0x604000175d80 [0x10306ec80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 18703, subsystem = 0x1070f1fe8, context = 0x0}}
8 : <CFRunLoopObserver 0x604000139780 [0x10306ec80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10655e6b3), context = <CFRunLoopObserver context 0x6000000d68f0>}
10 : <CFRunLoopSource 0x6040001762c0 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000144150, callout = __handleEventQueue (0x1068d5bb2)}}
11 : <CFRunLoopObserver 0x6040001395a0 [0x10306ec80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x105f78d92), context = <CFArray 0x6040002493c0 [0x10306ec80]>{type = mutable-small, count = 1, values = (
0 : <0x7fe21e008160>
)}}
12 : <CFRunLoopSource 0x600000175900 [0x10306ec80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000ab5e0, callout = FBSSerialQueueRunLoopSourceHandler (0x11165482f)}}
13 : <CFRunLoopSource 0x604000176140 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3103, callout = PurpleEventCallback (0x10fe7cbf7)}}
14 : <CFRunLoopObserver 0x6040001393c0 [0x10306ec80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x105fa7e1c), context = <CFRunLoopObserver context 0x7fe21de01090>}
15 : <CFRunLoopObserver 0x604000139320 [0x10306ec80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x105f78d92), context = <CFArray 0x6040002493c0 [0x10306ec80]>{type = mutable-small, count = 1, values = (
0 : <0x7fe21e008160>
)}}
16 : <CFRunLoopObserver 0x604000139460 [0x10306ec80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x105fa7da1), context = <CFRunLoopObserver context 0x7fe21de01090>}
17 : <CFRunLoopSource 0x604000178000 [0x10306ec80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22275, subsystem = 0x10710c668, context = 0x600000223c80}}
22 : <CFRunLoopSource 0x604000176680 [0x10306ec80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60400024b370, callout = __handleHIDEventFetcherDrain (0x1068d5bbe)}}
}
,
modes = <CFBasicHash 0x600000243ba0 [0x10306ec80]>{type = mutable set, count = 4,
entries =>
2 : <CFRunLoopMode 0x60000019de90 [0x10306ec80]>{name = UITrackingRunLoopMode, port set = 0x2e03, queue = 0x600000144570, source = 0x60000019df60 (not fired), timer port = 0x4e03,
sources0 = <CFBasicHash 0x60400024a620 [0x10306ec80]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x600000176740 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10fe7a75a)}}
2 : <CFRunLoopSource 0x6040001762c0 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000144150, callout = __handleEventQueue (0x1068d5bb2)}}
4 : <CFRunLoopSource 0x604000176680 [0x10306ec80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60400024b370, callout = __handleHIDEventFetcherDrain (0x1068d5bbe)}}
5 : <CFRunLoopSource 0x600000175900 [0x10306ec80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000ab5e0, callout = FBSSerialQueueRunLoopSourceHandler (0x11165482f)}}
}
,
sources1 = <CFBasicHash 0x60400024a5f0 [0x10306ec80]>{type = mutable set, count = 3,
entries =>
0 : <CFRunLoopSource 0x604000178000 [0x10306ec80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22275, subsystem = 0x10710c668, context = 0x600000223c80}}
1 : <CFRunLoopSource 0x604000176140 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3103, callout = PurpleEventCallback (0x10fe7cbf7)}}
2 : <CFRunLoopSource 0x604000175d80 [0x10306ec80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 18703, subsystem = 0x1070f1fe8, context = 0x0}}
}
,
observers = <1,6,0x60400024b2e0>,[0x6040001395a0--1283488] [0x604000139780--1283968] [0x604000139460--1283168] [0x604000139c80--1285248] [0x6040001393c0--1283008] [0x604000139320--1282848] ,
timers = (null),
currently 559045158 (106811992001349) / soft deadline in: 1.84466373e+10 sec (@ -1) / hard deadline in: 1.84466373e+10 sec (@ -1)
},

3 : <CFRunLoopMode 0x60400019d4d0 [0x10306ec80]>{name = GSEventReceiveRunLoopMode, port set = 0x2f03, queue = 0x604000144360, source = 0x60400019d5a0 (not fired), timer port = 0x3003,
sources0 = <CFBasicHash 0x600000244140 [0x10306ec80]>{type = mutable set, count = 1,
entries =>
0 : <CFRunLoopSource 0x600000176740 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10fe7a75a)}}
}
,
sources1 = <CFBasicHash 0x600000244170 [0x10306ec80]>{type = mutable set, count = 1,
entries =>
1 : <CFRunLoopSource 0x604000176200 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3103, callout = PurpleEventCallback (0x10fe7cbf7)}}
}
,
observers = (null),
timers = (null),
currently 559045158 (106811993680557) / soft deadline in: 1.84466373e+10 sec (@ -1) / hard deadline in: 1.84466373e+10 sec (@ -1)
},

4 : <CFRunLoopMode 0x60000019db50 [0x10306ec80]>{name = kCFRunLoopDefaultMode, port set = 0x2103, queue = 0x6000001442b0, source = 0x60000019dc20 (not fired), timer port = 0x5403,
sources0 = <CFBasicHash 0x60400024a650 [0x10306ec80]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x600000176740 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10fe7a75a)}}
2 : <CFRunLoopSource 0x6040001762c0 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000144150, callout = __handleEventQueue (0x1068d5bb2)}}
4 : <CFRunLoopSource 0x604000176680 [0x10306ec80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60400024b370, callout = __handleHIDEventFetcherDrain (0x1068d5bbe)}}
5 : <CFRunLoopSource 0x600000175900 [0x10306ec80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000ab5e0, callout = FBSSerialQueueRunLoopSourceHandler (0x11165482f)}}
}
,
sources1 = <CFBasicHash 0x60400024a3e0 [0x10306ec80]>{type = mutable set, count = 3,
entries =>
0 : <CFRunLoopSource 0x604000178000 [0x10306ec80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22275, subsystem = 0x10710c668, context = 0x600000223c80}}
1 : <CFRunLoopSource 0x604000176140 [0x10306ec80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3103, callout = PurpleEventCallback (0x10fe7cbf7)}}
2 : <CFRunLoopSource 0x604000175d80 [0x10306ec80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 18703, subsystem = 0x1070f1fe8, context = 0x0}}
}
,
observers = <1,6,0x60400024b4c0>,[0x6040001395a0--1283488] [0x604000139780--1283968] [0x604000139460--1283168] [0x604000139c80--1285248] [0x6040001393c0--1283008] [0x604000139320--1282848] ,
timers = <CFArray 0x6000000aa7a0 [0x10306ec80]>{type = mutable-small, count = 1, values = (
0 : <CFRunLoopTimer 0x600000176c80 [0x10306ec80]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 559045126 (-32.3797849 @ 106779616101844), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x103dac849 / 0x10646931b) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x60000026a700>}
)},
currently 559045158 (106811993736551) / soft deadline in: 1.8446744e+10 sec (@ 106779616101844) / hard deadline in: 1.8446744e+10 sec (@ 106779616101844)
},

5 : <CFRunLoopMode 0x60400019d810 [0x10306ec80]>{name = kCFRunLoopCommonModes, port set = 0x440b, queue = 0x6040001448e0, source = 0x60400019db50 (not fired), timer port = 0x3f0b,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 559045158 (106811995881018) / soft deadline in: 1.84466373e+10 sec (@ -1) / hard deadline in: 1.84466373e+10 sec (@ -1)
},

}
}

App启动后系统添加了6个Observer,其中2个的回调都是_wrapRunLoopWithAutoreleasePoolHandler。

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

回调是_UIGestureRecognizerUpdateObserver的 Observer 在 BeforeWaiting(即将进入休眠) 时执行GestureRecognizer的回调

回调是_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv的 Observer 在BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 时会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面

关于网络

Runloop & CFSocket

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
static void __CFSocketSendNameRegistryRequest(CFSocketSignature *signature, CFDictionaryRef requestDictionary, __CFSocketNameRegistryResponse *response, CFTimeInterval timeout) {
CFDataRef requestData = NULL;
CFSocketContext context = {0, response, NULL, NULL, NULL};
CFSocketRef s = NULL;
CFRunLoopSourceRef source = NULL;
if (NULL != response->error) *(response->error) = kCFSocketError;
requestData = CFPropertyListCreateXMLData(kCFAllocatorSystemDefault, requestDictionary);
if (NULL != requestData) {
if (NULL != response->error) *(response->error) = kCFSocketTimeout;
s = CFSocketCreateConnectedToSocketSignature(kCFAllocatorSystemDefault, signature, kCFSocketDataCallBack, __CFSocketHandleNameRegistryReply, &context, timeout);
if (NULL != s) {
if (kCFSocketSuccess == CFSocketSendData(s, NULL, requestData, timeout)) {
source = CFSocketCreateRunLoopSource(kCFAllocatorSystemDefault, s, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, __kCFSocketRegistryRequestRunLoopMode);
CFRunLoopRunInMode(__kCFSocketRegistryRequestRunLoopMode, timeout, false);
CFRelease(source);
}
CFSocketInvalidate(s);
CFRelease(s);
}
CFRelease(requestData);
}
}

static void __CFSocketHandleRead(CFSocketRef s, Boolean causedByTimeout) { ... }

static void __CFSocketHandleWrite(CFSocketRef s, Boolean callBackNow) { ... }

...

CFSocket 是最底层的接口,只负责 socket 通信。CFNetwork 是基于 CFSocket 等接口的上层封装。

RunLoop 的实例

AFNetworking

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
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}

+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}

- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
-------------本文结束感谢您的阅读-------------