深入分析Runloop

  一般写的程序都是像下面这样的,代码一行行执行,到return的时候代码都已经执行完毕,程序退出。

1
2
3
4
int main(int argc, char * argv[]) {
/* 代码段 */
return 0;
}

  但实际的应用中,我们的软件不可能像这样线性的执行,执行完我们的软件就退出,所以就需要一种技术,让我们的软件始终保持运行状态。在iOS系统中,这项技术就是Runloop。

0x01 主要的结构信息

1. CFRunloop

  Runloop都会对应一个线程。而且Runloop可以有多种模式,但是当前使用的只能是一种模式。我们的CFRunloop结构如下

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; // 保存的commonMode
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; // 当前模式
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};

2. CFRunLoopMode

  Mode管理着各种事件,我们的source0、source1、observer、timer都归mode管理。每个模式下又包含多个source0,source1,observer和timer。不同mode下的source0、source1、observer、timer都是隔离开的。

  如果Mode里面没有source0,source1,observer和timer,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
struct __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; // mach_port_t对应的CFRunLoopSourceRef
__CFPortSet _portSet; // 所有要监听的mach_port_t
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
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};

3. CFRunLoopSource

  source分为source0和source1。

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

  source0一般都是应用的内部事件,比如触摸事件、CFSocket等。

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;

  source1一般与mach_port通信,所以接收的是内核态的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info); // 相比source0多了mach_port_t,即端口,用于线程之间通信
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

4. CFRunLoopObserver

  观察runloop的各种状态。

1
2
3
4
5
6
7
8
9
10
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 */
};

  可被观察的状态有如下几种

1
2
3
4
5
6
7
8
9
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 进入runloop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入睡眠
kCFRunLoopAfterWaiting = (1UL << 6), // 唤醒后
kCFRunLoopExit = (1UL << 7), // 退出runloop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};

5. CFRunLoopTimer

  可以在设定的时间到达后触发回调。

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

0x02 获取线程

1. CFRunLoopGetMain

  内部调用了_CFRunLoopGet0函数,传入的参数是主线程。

1
2
3
4
5
6
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}

2. CFRunLoopGetCurrent

  跟CFRunLoopGetMain函数一样,调用的同样是_CFRunLoopGet0函数,只是参数不同,这里的入参是当前所在线程。

1
2
3
4
5
6
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}

3. CFRunLoopGet0

  既然CFRunLoopGetMainCFRunLoopGetCurrent都调用了_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
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 如果线程为空,默认视作主线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
// __CFRunLoops是个CFMutableDictionaryRef类型的全局变量,用来保存RunLoop。这里首先判断有没有这个全局变量,如果没有就新创建一个这个全局变量,并同时创建一个主线程对应的runloop。
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建一个主线程对应的runloop。
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 交换dict和__CFRunLoops,也就是这里开始__CFRunLoops就是刚刚创建的那个全局变量
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 根据线程取其对应的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果这个线程没有对应的runloop,就新建立一个runloop对象
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
// 二次确认,是否的确没有该线程对应的runloop
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 如果的确没有对应的runloop,就保存进全局变量中
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
// 注册一个回调,当线程销毁时一同销毁对应的runloop
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}

  获取线程流程总结如下:

  • 有个全局变量保存着各个线程与各个runloop对象的关系,该变量初始化的时候会同时创建一个主线程对应的runloop对象。
  • 子线程的runloop默认是获取的时候才开始创建。所以多线程环境中,只有主线程的runloop是一开始就创建出来的,其他线程被创建的时候并不会一起创建一个runloop对象。
  • runloop对象的生命周期和线程的生命周期同步。

0x03 创建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
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
CFRunLoopRef loop = NULL;
CFRunLoopModeRef rlm;
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
if (NULL == loop) {
return NULL;
}
(void)__CFRunLoopPushPerRunData(loop);
__CFRunLoopLockInit(&loop->_lock);
loop->_wakeUpPort = __CFPortAllocate();
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
__CFRunLoopSetIgnoreWakeUps(loop);
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
loop->_commonModeItems = NULL;
loop->_currentMode = NULL;
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
loop->_blocks_head = NULL;
loop->_blocks_tail = NULL;
loop->_counterpart = NULL;
loop->_pthread = t;
loop->_winthread = 0;
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}

static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
CHECK_FOR_FORK();
CFRunLoopModeRef rlm;
struct __CFRunLoopMode srlm;
memset(&srlm, 0, sizeof(srlm));
_CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);
srlm._name = modeName;
rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
if (NULL != rlm) {
__CFRunLoopModeLock(rlm);
return rlm;
}
if (!create) {
return NULL;
}
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;
// 新建一个runloop的队列,用来处理timer相关事件
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
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

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

CFSetAddValue(rl->_modes, rlm);
CFRelease(rlm);
__CFRunLoopModeLock(rlm); /* return mode locked */
return rlm;
}

  构建一个mode的时候会同时创建一个GCD Queue,用来处理时间相关的任务。

0x04 运行RunLoop

  runloop内部其实就是一个do…while()循环。

1
2
3
4
5
6
7
void CFRunLoopRun(void) {	/* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

  调用的是CFRunLoopRunSpecific函数,用kCFRunLoopDefaultMode模式进行启动

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
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根据模式名字,找到对应模式
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 如果没有找到这个模式或者这个模式里是空的(source0\source1\timers都为空),就退出runloop循环
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
// 取出runloop当前的模式
CFRunLoopModeRef previousMode = rl->_currentMode;
// 将runloop的模式改为传进来的默认模式
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;

// 通知observers要进入runloop了
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 主要工作内容还是在__CFRunLoopRun
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知observers要退出runloop了
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

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

  看下最重要的__CFRunLoopRun函数

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
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
// 获取当前mach的时间,纳秒级精度
uint64_t startTSR = mach_absolute_time();

// 如果runloop暂停或者mode暂停,那么就退出循环
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}

// 如果是主线程并且是默认mode,将gcd获取主线程的端口赋值给dispatchPort,以便和主线程通信
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
// 将gcd中根队列的端口赋值给modeQueuePort
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
// cgd根队列管理着所有的子队列,获取根队列的端口,方便与其通信,来处理后面传递进队列的任务
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

// 看入参的超时时间,如果是在可取范围内的,就设置一个定时器,如果时间到了就跳到__CFRunLoopTimeout处理超时后要做的事。
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);
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]; // 消息缓存池
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
......
__CFPortSet waitSet = rlm->_portSet; // 需要监听的port

__CFRunLoopUnsetIgnoreWakeUps(rl);
// 通知observers即将处理timer事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知observers即将处理source0事件
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 执行当前runloop链表内的每个block任务,即CFRunLoopPerformBlock加入的block任务
__CFRunLoopDoBlocks(rl, rlm);
// 处理source0事件
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 如果有再次处理block任务
__CFRunLoopDoBlocks(rl, rlm);
}

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 如果有source1,处理source1事件
// 循环中第一次的循环didDispatchPortLastTime为YES,所以这个分支在第一次循环的时候不会进入
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;
}
}

didDispatchPortLastTime = false;
// 通知observers即将休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 设置为休眠状态
__CFRunLoopSetSleeping(rl);

__CFPortSetInsert(dispatchPort, waitSet);

__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);

CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

// 睡眠中,有个循环接收端口消息
do {
if (kCFUseCollectableAllocator) {
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;

// 内部调用mach_msg函数接收消息,以便接收到消息立刻唤醒runloop,如果没有消息,就让线程休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 如果收到来自runloop queue队列的事件,里面一般都是timer相关事件
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// 按个取出任务执行
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {

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

__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);

rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

__CFPortSetRemove(dispatchPort, waitSet);

__CFRunLoopSetIgnoreWakeUps(rl);

// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 通知observers即将醒来
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();
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// 被timer相关唤醒,处理timer
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
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();
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);
// 内部是_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();

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);
// 有source1事件处理,就处理source1事件
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);
}
}

// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);

}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
// 处理加入到loop的block
__CFRunLoopDoBlocks(rl, rlm);


if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) { // 如果runloop被停止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) { // 如果mode被停止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { // 如果模式里是空的
retVal = kCFRunLoopRunFinished;
}

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif

} while (0 == retVal);

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

return retVal;
}

  启动runloop主要流程

  1. 通知observers即将进入runloop
  2. 通知observers即将处理timers事件
  3. 通知observers即将处理source事件
    • 处理blocks
    • 处理source0
  4. 通知observers即将休眠(内部有个循环接收消息)
  5. 通知observers结束休眠,并回到第2步
  6. 处理消息
    • 处理timer事件
    • 处理主线程任务
    • 处理blocks
    • 回到第2步
  7. 通知observers即将退出runloop

0x05 使用

NSTimer

  在RunLoop中,关于定时器经常遇到的问题就是滑动的时候定时器就不走动了。解决方案也是很熟悉,把timer加入NSRunLoopCommonModes 或者同时加入到NSDefaultRunLoopModeUITrackingRunLoopMode

1
2
3
4
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
或者
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:UITrackingRunLoopMode];

  其中NSRunLoopCommonModes其实是一个mode集合,里面包括所有的被标记为common的mode,比如NSDefaultRunLoopModeUITrackingRunLoopMode

  RunLoop同一时刻下只能跑在一个mode上,所以Timer不走动就是因为如果只加入到一种mode下,切换到别的mode,比如滑动页面就会切换到UITrackingRunLoopMode,Timer就因为在该模式下没有注册,所以不会响应Timer事件。

线程保活

  比如子线程内请求一个网络数据,但是等待完成需要等一会,这就会导致得到数据的时候子线程就被回收掉了。要想子线程一直存在,我们可以利用RunLoop,在子线程写下如下代码就可以使得线程保活。

1
2
3
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];

  但是这样又会有一个问题,我们看下对于run方法的描述

1
2
3
Puts the receiver into a permanent loop, during which time it processes data from all attached input sources.
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. macOS can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

  简单来说就是run方法内部是个无限循环,无限调用runMode:beforeDate:方法,并且即时删除mode下所有的source和timer也不能停止。如果创建多个线程,并都通过run启用RunLoop,就会造成内存泄露的问题,如果想要其变得可控,官方也给了建议,自己手动写了while,并且通过一个全局变量来控制什么时候结束这个循环。

1
2
3
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

  停止RunLoop的伪代码可以写成这样的

1
2
3
4
- (void)stop {
self.shouldKeepRunning = NO;
CFRunLoopStop(CFRunLoopGetCurrent());
}

  我们总结一下启动RunLoop的几种方式

  • run

    无限循环,终止不掉

  • runUntilDate:

    内部同样重复调用runMode:beforeDate:,但是时间到了就结束,不再调用;或者通过CFRunLoopStop结束

  • runMode:beforeDate:

    只调用一次;或者通过CFRunLoopStop结束

AutoreleasePool

  我们知道[NSRunLoop currentRunLoop]用来获取当前RunLoop,如果没有就会创建一个RunLoop,我们看下其内部汇编代码,发现会自动开启AutoreleasePool。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Foundation`+[NSRunLoop(NSRunLoop) currentRunLoop]:
0x1853c7ea4 <+0>: stp x20, x19, [sp, #-0x20]!
0x1853c7ea8 <+4>: stp x29, x30, [sp, #0x10]
0x1853c7eac <+8>: add x29, sp, #0x10 ; =0x10
0x1853c7eb0 <+12>: mov x0, #0x0
0x1853c7eb4 <+16>: bl 0x185416114 ; NSPushAutoreleasePool
0x1853c7eb8 <+20>: mov x19, x0
0x1853c7ebc <+24>: bl 0x18495545c ; CFRunLoopGetCurrent
0x1853c7ec0 <+28>: bl 0x184958a74 ; _CFRunLoopGet2
0x1853c7ec4 <+32>: mov x20, x0
0x1853c7ec8 <+36>: mov x0, x19
0x1853c7ecc <+40>: bl 0x185416118 ; NSPopAutoreleasePool
0x1853c7ed0 <+44>: mov x0, x20
0x1853c7ed4 <+48>: ldp x29, x30, [sp, #0x10]
0x1853c7ed8 <+52>: ldp x20, x19, [sp], #0x20
0x1853c7edc <+56>: ret