Runloop_common_mod

之前对 runloop 的 common mod 有点模糊,今天看了下 CFRunloop 的源码,感觉清晰了不少。

TL;DR

当你调用 CFRunLoopAddCommonMode 把一个 runloop 的某个 mode 标记为 commonMode 的时候,其实做的操作就是把当前 runloop 的 commonModeItem 中的 source timer observer 注册到这个 mode 中。

当调用 CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName) 之类的方法添加一个 source/timer/observer 时,如果 modeName 是 kCFRunLoopCommonModes 的话,会把这个 source 添加到 commonModeItems 里,然后把这个 source/timer/observer 添加到 runloop 的所有 mode 中(遍历 runloop 的 modes,根据名称找到 mode,然后添加进去)

首先我们看下 CFRunLoopModeRef 的定义

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
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

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

我们再看下 CFRunLoop 的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;

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; // 一个 set, 里边存放的是字符串,mode name
CFMutableSetRef _commonModeItems; // 被标记为 common 的 source,一个 set 里边可能是 CFRunLoopSourceRef 或 CFRunLoopObserverRef 或 CFRunLoopTimerRef
CFRunLoopModeRef _currentMode; // 当前的 mode
CFMutableSetRef _modes; // 所有的 mode 集合,是一个 set 里边存的是 CFRunLoopModeRef
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};

我们可以看到,每个 runloop 中都有若干个 modes,每个 mode 里边会有诺干个 source,timer,observer

我们可以通过下边的方法把一个 mode 标记为 common mod

1
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);

也可以通过下边的方法,把一个 source 添加或移除到 runloop 的 model 中,当然这个 mode 可以是 kCFRunLoopCommonModes

1
2
3
4
5
6
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

首先我们来看下 CFRunLoopAddCommonMode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
__CFRunLoopLock(rl);
if (!CFSetContainsValue(rl->_commonModes, modeName)) { // 如果 commonModes
CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL; // 根据 commonModeItems 创建一个 set,
CFSetAddValue(rl->_commonModes, modeName); // 把 当前的 modeName 存储到 commonModes 里边
if (NULL != set) {
CFTypeRef context[2] = {rl, modeName};
/* add all common-modes items to new mode */
/*
这里相当于一个 for 循环,遍历 set 然后调用 __CFRunLoopAddItemsToCommonMode 方法
__CFRunLoopAddItemsToCommonMode 第一个参数是 set 中的 item,第二个参数是 context
*/
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
CFRelease(set);
}
} else {}
__CFRunLoopUnlock(rl);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 这个方法就很简单了,判断 item 的类型,根据不同的类型调用不同的方法,把 item 添加到对应的 runloop 的对应的 model 中
static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx) {
CFTypeRef item = (CFTypeRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
}
}

我们看 CFRunLoopAddSource observer 和 timer 的操作 大同小异。observer 的话由于是个数组,会有优先级的问题。 倒序遍历数组,插入到把 observer 插入到数组中,结束时,数组里的 observer 的 order 是从小到大排列的。

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
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {	/* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rls)) return;
Boolean doVer0Callout = false;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) { // 如果指定的 modeName 是 kCFRunLoopCommonModes 的话
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; // 创建一个 set,这 set 里边是那些被标记为 common 的 mode
if (NULL == rl->_commonModeItems) { // 如果 _commonModeItems 为空,则创建一个
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
CFSetAddValue(rl->_commonModeItems, rls); // 把当前的 source 加到 _commonModeItems 中
if (NULL != set) { // 如果 set 不为空的话,则遍历这个 set,调用 __CFRunLoopAddItemToCommonModes 方法
CFTypeRef context[2] = {rl, rls};
/* add new item to all common-modes */
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else { // 如果不是 modeName 不是 kCFRunLoopCommonModes
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true); // 找到这个 mode
if (NULL != rlm && NULL == rlm->_sources0) { // 没有 source0 的话,创建
rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
}
if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) { // 如果 source0 和 source1 都不存在的话,根据 source 类型,添加到对应的 set 中
if (0 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources0, rls);
} else if (1 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources1, rls);
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
__CFPortSetInsert(src_port, rlm->_portSet);
}
}
__CFRunLoopSourceLock(rls);
if (NULL == rls->_runLoops) { // 如果 source 没有 runloop 的话,创建一个 CFMutableDictionary
rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
}
CFBagAddValue(rls->_runLoops, rl); // 把 runloop 加入到字典中,key 和 value 是一样的
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) { // 查看 source0 是否有 callOut
if (NULL != rls->_context.version0.schedule) {
doVer0Callout = true;
}
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
if (doVer0Callout) {
// although it looses some protection for the source, we have no choice but
// to do this after unlocking the run loop and mode locks, to avoid deadlocks
// where the source wants to take a lock which is already held in another
// thread which is itself waiting for a run loop/mode lock
rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
}

总的来说呢,就是当你调用 CFRunLoopAddCommonMode 把一个 runloop 的 mode 标记为 commonMode 的时候,其实做的操作就是把当前 runloop 的 commonModeItem 中的 source timer observer 注册到这个 mode 中。在 runloop 的循环中,直接处理 mode 中的 source, timer, observer 即可