源码比文字更令人深刻
版本: CF-1151.16
为了文章简洁,只摘抄部分主要的代码
源码详细介绍在这里
一、邂逅runLoop
应该是一个美丽的下午,在一场面试上,遇见了runLoop,可惜擦肩而过。。。
二、认识runLoop
CFRunLoop
|
|
从源码可以看出一部分内容 :
一个runLoop对象,主要包含一个线程_pthread
,一个用来被唤醒的端口_wakeUpPort
,一个当前运行的mode_currentMode
,以及若干个_modes
、_commonModes
、_commonModeItems
。
runLoop有很多mode,即_modes
,但是只有一个_currentMode
,runLoop一次只能运行在一个mode下,不可能在多个mode下同时运行。
CFRunLoopMode
|
|
从mode的组成可以看出来:mode管理了所有的事件(sources/timers/observers),而runLoop是管理mode的
CFRunLoopSource
|
|
源码中看出来,source0和source1的区别,source1比source0多一个接收消息的端口mach_port_t
CFRunLoopObserver
|
|
_activities
状态值:
CFRunLoopTimer
|
|
三、了解runLoop
5个类之间的主要方法,来详细了解类之间的相互关系
CFRunLoopCopyCurrentMode
获取runLoop正在运行的mode(即
_currentMode
)的name。
|
|
CFRunLoopCopyAllModes
返回一个数组,其中包含了runLoop所有定义过的mode(即
_modes
)的name
|
|
CFRunLoopAddCommonMode
向runLoop的commonModes添加一个mode
|
|
CFRunLoopAddSource
添加一个source到指定的runLoopMode
|
|
CFRunLoopAddObserver
添加rlo到指定的rlm
|
|
内部实现CFRunLoopSource
跟差不多,都是根据mode是否commonMode分两种情况,差别在于:
关联mode:mode有一个数组
_observers
,添加是根据rlo的_order
进行添加的关联rl:根据
_rlCount
是否为0。只有当rlo的_rlCount
为0时,其_runLoop
才是rl。
CFRunLoopAddTimer
添加rlt到指定的rlm
|
|
内部实现同上,区别:
rlt只能添加到其
_runLoop
的mode中,如果rl不是其_runLoop
,直接返回12345678if (NULL == rlt->_runLoop) {rlt->_runLoop = rl;} else if (rl != rlt->_runLoop) {__CFRunLoopTimerUnlock(rlt);__CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl);return;}rlt有一个变量
_rlModes
,其存储的是rlt所在的mode的name1CFSetAddValue(rlt->_rlModes, rlm->_name);rlm有一个变量
_timers
,其存储timer是根据timer的启动时间,即_fireTSR
,进行排序的
四、获取runLoop
runLoop跟其所在线程是一一对应的
API提供了两个获取runLoop的方法
123456789101112131415CFRunLoopRef CFRunLoopGetMain(void) {static CFRunLoopRef __main = NULL; // no retain needed// pthread_main_thread_np() 主线程if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS neededreturn __main;}CFRunLoopRef CFRunLoopGetCurrent(void) {CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if (rl) return rl;// pthread_self() 当前线程return _CFRunLoopGet0(pthread_self());}其中,
TSD
是thread special data,表示线程私有数据,在 C++ 中,全局变量可以被所有线程访问,局部变量只有函数内部可以访问。而 TSD 的作用就是能够在同一个线程的不同函数中被访问。(找到的资料)__CFTSDKeyRunLoop
是一个枚举类型的关键字。pthread_self()
可以得知,如果要获取非主线程的runLoop,必须在该线程内部调用CFRunLoopGetCurrent
才能获取。根据线程t获取对应的runLoop
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162// 一个内部全局的字典static CFMutableDictionaryRef __CFRunLoops = NULL;CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {// 1. 保证t不为空if (pthread_equal(t, kNilPthreadT)) {t = pthread_main_thread_np();}// 2. 创建全局字典,并存储主线程的runLoopif (!__CFRunLoops) {CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);// 通过pthread_main_thread_np()创建CFRunLoopRef类型的mainLoop,内部对其所有变量进行初始化,并且赋值_pthread为pthread_main_thread_np()CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());// key是主线程的指针, value 是刚创建的mainLoopCFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);// 比较并交换指针,// 这里比较第一个参数NULL和第三个参数 (void * volatile *)&__CFRunLoops全局字典,如果相等,系统会自动把第二参数的值赋给第三个参数,// volatile的作用是 每次取得数值的方式是直接从内存中读取if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {CFRelease(dict);}// coreFoundation 要手动管理内存, create 对应 releaseCFRelease(mainLoop);}// 3. 全局字典已经存在,从中获取对应线程t的runLoopCFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));// 如果获取不到loop,if (!loop) {// 根据 t 创建 一个newLoopCFRunLoopRef newLoop = __CFRunLoopCreate(t);// 再一次进行获取loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));// 如果还不存在,就直接赋值,if (!loop) {CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);loop = newLoop;}}// 4. 注册TSDif (pthread_equal(t, pthread_self())) {// 注册回调,当线程销毁时,顺便也销毁其对应的 RunLoop_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);}}return loop;}
线程和runLoop是一一对应,保存在一个全局字典里,主线程的runLoop是在初始化字典时已经创建好了,其他线程的runLoop只有在获取的时候才会创建。
五、运行runLoop
CFRunLoopRun
默认情况下,运行当前线程的runLoop
|
|
源码得知:
kCFRunLoopDefaultMode
,默认情况下,runLoop是在这个mode下运行的,- runLoop的运行主体是一个do..while循环,除非停止或者结束,否则runLoop会一直运行下去
CFRunLoopRunInMode
在指定的mode下运行当前线程的runLoop
|
|
该方法,可以设置runLoop运行在哪个mode下modeName
,超时时间seconds
,以及是否处理完事件就返回returnAfterSourceHandled
。
这两个方法实际调用的是同一个方法CFRunLoopRunSpecific
,其返回是一个SInt32
类型的值,根据返回值,来决定runLoop的运行状况。
CFRunLoopRunSpecific
在指定的mode下,运行指定的runLoop
|
|
这里有3点:
- kCFRunLoopRunFinished mode中没有事件处理,直接返回
- kCFRunLoopEntry runLoop即将开始运行,通知observers
- kCFRunLoopExit runLoop 即将退出,通知observers
__CFRunLoopRun
这里处理了runLoop从开始运行到退出的所有逻辑
|
|
上述2-10就是runLoop运行过程中的循环逻辑,而最终返回的状态有:kCFRunLoopRunFinished
、kCFRunLoopRunStopped
、kCFRunLoopRunTimedOut
以及kCFRunLoopRunHandledSource
四种枚举类型
六、总结:
1. runLoop跟线程一一对应,非主线程的rl只能在其内部获取,runLoop管理rlm和回调block,而rlm存储了所有的事件。
2. runLoop运行核心就是一个do..while循环,遍历所有事件,有事件处理,无事件休眠,直至达到退出条件。
3. 以上就是runLoop内部的源码分析,当然会有理解不到位的情况,也留有待解决的问题,万望不吝赐教。
参考资料: