注:RunLoop源码下载地址,下载号最大的压缩包。RunLoop的源码在CFRunLoop.h/.c两个文件中。
1 RunLoop简介
runloop是一个对象。这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行所有的Event Loop的逻辑。这个对象有两个版本NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
2 Mode和Source
先把runloop对象的源码定义贴出:
// 对外开发的接口
typedef struct __CFRunLoop * CFRunLoopRef
// runloop对象的定义,不对外开发
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;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
需要注意的是__CFRunLoop是.C文件中不对外开放的,因此调试的时候拿到CFRunLoopRef结构体也没法看其结构。但是可以把CFRunLoopRef结构体打印出来,就可以看其结构了。
首先说结论:RunLoop、Mode和Source/timer/observer的关系是,Runloop对象包含多个mode对象,每个mode对象又包含不同的source/timer/observer对象。
源码怎么体现呢?可以看到在源码中有三个属性:_commonModes,_commonModeItems和_modes。其中_modes会存储该runloop所有的mode对象。_commonModes和_commonModeItems是为了处理"NSRunLoopCommonModes"。以下把NSRunLoopCommonModes简称为commonMode。commonMode不是一个实际存在mode。可以这么理解,commonMode是一份协议。
举个例子:在主线程中NSDefaultRunLoopMode和UITrackingRunLoopMode遵守这份协议,那么_commonModes就会包含这两个mode的名称。以后只要主线程往commonMode中加source/timer/observer,另外两个mode也会自动把这些源加入到自己的对象中。
接下来就通过源码看下如何实现这个。
Mode和Source的内部实现
以下是创建runloop对象的方法:
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);
// 默认情况下,commonMode“等于”kCFRunLoopDefaultMode
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;
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
loop->_winthread = 0;
#endif
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}
_commonModes在Runloop初始化的时候就会创建成一个可变集合对象。并且添加一个KCFRunLoopDefaultMode进去。_commonModes只用来保存mode的名字,因此,改集合中所有元素都是CFString对象。
_commonModeItems在初始化RunLoop初始化的时候等于NULL。在下面将看到只有往runloop的commonMode添加timer/source/observer时才会创建为可变的集合,并把这么资源放到里面。由此可以看出,commonMode的特殊性,它不是一个独立的_CFRunLoopMode的结构体对象,而是由_commonModeItems和_commonModes共同组成commonMode。
_modes存储该runloop对象用到的所有mode对象。
Mode对象现身
说了这么久,让我们看下Mode对象是什么?以下代码就是Mode的定义:
// 位于CFRunLoop.C未对外开发
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
// 位于CFRunLoop.C未对外开发
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 */
};
CFRunLoopModeRef和__CFRunLoopMode都在CFRunLoop.C定义的。可以看到苹果不允许我们自定义CFRunLoopModeRef对象添加到runloop中。我们只能通过NSRunLoop.h中有限的方法向runloop加源。说白了,苹果不想我们对runloop做过多的操作。
可以看到这里存在四个集合对象:_sources0,_sources1,_observers和_timers。一个mode对象中所有能触发runloop的源都存在于在这四个集合中。
通过上面的我们基本知道runloop的持有一堆CFRunLoopModeRef对象,放在自己的_modes属性中。每个CFRunLoopModeRef对象持有一堆的“源”。以及虚假的commonMode。接下来我们通过添加源到runloop对象中看,以上的结构是如何构成的。
添加源到runloop
首先看下有哪些方法可以添加源到runloop中。苹果给我们了三个方法,如下:
/**
@param rl runloop对象
@param source 将要添加的源
@param mode 将要存储该源的mode的名称
*/
CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
以下以timer未例把整个流程走一次:
runloop.png
上图流程图中注意点解释:
注意点1:_commonModes中存的是支持commonModemode名称。将这些名称复制到新的集合中,以便在注意点2处使用;
注意点2:CFSetApplyFunction方法的作用是对参数set中的每个item都调用__CFRunLoopAddItemToCommonModes方法。__CFRunLoopAddItemToCommonModes方法的第一参数就是set中遍历到的item,第二个是context。
注意点3:该方法会查找名为modename的mode是否在runloop对象中,如果有就返回。如果没有,就要注意该方法第三个参数为true,那么就会创建一个名为modename的mode返回。
整个流程:
- 添加timer并传三个参数:runloop对象、类型为CFRunLoopTimerRef的timer对象、modename;
- modename是否是kCFRunLoopCommonModes,如果是到第三步,否则直接到第8步;
- 将_commonModes中的字符串(参见注意点1)复制到set中,并将timer对象添加到_commonModeItems中;
- 调用CFSetApplyFunction,对set中的每个item调用__CFRunLoopAddItemToCommonModes方法。在__CFRunLoopAddItemToCommonModes中跳到步骤5;
- 查看名为modename的mode是否在runloop对象中存在,不存在则创建,得到rlm对象;
- 将timer对象添加到rlm对象的timers集合中;
- 将rlm对象添加到runloop的_modes对象中。
CommonMode和DefaultMode的区别
这里要区分主线程和子线程,苹果是区别处理的。
子线程的CommonMode和DefaultMode
子线程的情况比较简单,在runloop对象的_modes中,只有KCFRunLoopDefaultMode,没有名为"NSRunLoopCommonModes"的Mode。
通常我们会以下两个方法来添加timer/source:
// NSRunLoop
[runloop addTimer:timer forMode:NSRunLoopCommonModes];
[runloop addPort:timer forMode:NSRunLoopCommonModes];
所有通过以上方法往NSRunLoopCommonModes中添加的源,首先会被添加在_commonModeItems的集合中,如流程图中的步骤3。然后被加在_modes中KCFRunLoopDefaultMode的Mode中,如流程图中的4和5。NSRunLoopCommonModes是一个虚拟的mode,它在_modes集合和整个runloop中对象都不存在该名称的mode对象。但是需要注意的是直接往"KCFRunLoopDefaultMode"中添加的源不会填加到_commonModeItems的集合中。可以这么认为,_commonModeItems中只包含往"NSRunLoopCommonModes"这个虚拟Mode中加的源。而_modes中名为KCFRunLoopDefaultMode的Mode是一个真实存在的mode,它是NSRunLoopCommonModes和KCFRunLoopDefaultMode源的集合。
子线程的runloop初始时只有一个KCFRunLoopDefaultMode。
主线程的CommonMode和DefaultMode
主线程和子线程类似。主线程runloop初始时_modes中有三个mode对象:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 、 UITrackingRunLoopMode和GSEventReceiveRunLoopMode。当往主线程的NSRunLoopCommonModes中添加源时。也会先往_commonModeItems中添加,然后会分别往_modes中的UITrackingRunLoopMode和kCFRunLoopDefaultMode都添加一份。因为UITrackingRunLoopMode和kCFRunLoopDefaultMode支持NSRunLoopCommonModes。这也就是加在commonModes中的定时器无论是否滑动都会触发。
但是奇怪的是主线程runloop对象的_modes中真实存在一个名为"KCFRunLoopCommonModes"的Mode对象。但是该对象中source,timer和observer都是空。
这里需要注意的是,即使把timer加在主线程的NSRunLoopCommonModes中也不一定保证timer一定会准时,因为主线程还可能运行在GSEventReceiveRunLoopMode中。甚至是我们自定义的mode,虽然这不常见。
外传
这里有三个方法需要注意:
// 方法1 唯一的往_commonMode集合中添加内容的方法
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName)
// 方法2 它会把多个items添加到commonMode中
static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx);
// 方法3 最常用的添加源到commonMode中
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx);
首先说明的是方法1没有地方调用。应该是苹果并不想我们通过该方法往_commonMode中加内容。该方法需要两个参数rl即要操作的runloop,modename即要添加_commonMode中的字符串。这里就不能保证_commonMode中的名称是唯一的(KCFRunLoopDefaultMode)。因为Mode是用名字区分的。如果commonMode的名称不是唯一的岂不乱了。
方法2只在方法1中调用,也就说明,方法2也是没有地方调用的。其中valeu是要添加的源
即source/timer/observer。ctx是一个void的数组。ctx[0]是runloop对象,ctx[1]是mode name。
方法3是往commonMode中添加源的正常方法。value是mode name。 ctx也是是一个void的数组,ctx[0]是runloop对象,ctx[1]是要添加的源,即source/timer/observer。注意该方法与方法2参数的区别。
网友评论