-
准备工作
RunLoop
源码下载地址 -
RunLoop 概念
-
RunLoop 简介
RunLoop
其实就是一种事务处理的循环,是事件接受、分发的机制的实现,用来不停的调度工作以及处理输入事件。其本质就是一个do-while
循环,如果有输入则执行对应事件,没有则休息,这里RunLoop
的do-while
和普通的do-while
循环不一样,普通的循环会让cpu
处于忙等待的情况,当cpu
跑满了也就崩溃了。而runloop
的do-while
循环是一种闲等待,也就是不会消耗cpu
所以runloop
具有休眠功能。
下图是runloop
运行原理图:
-
-
RunLoop 作用
- 保持程序持续运行。程序启动就会自动开启一个
runloop
在主线程 - 处理App中的各种事件
- 节省CPU资源,提高程序性能 有事情则做事,没事情则休眠
- 保持程序持续运行。程序启动就会自动开启一个
-
RunLoop 和线程之间的关系
从上文中的
runloop
运行原理图可以知道两者之间的关系是一一对应的关系 -
RunLoop 底层源码分析
-
RunLoop
本质RunLoop
底层其实就是一个结构体,是一个__CFRunLoop
对象,具体结构如下:
从对象的结构中可以得出当前运行的mode
只会有一个,但是每个runloop
里面是存在多个mode
的同时也存在着多个item
也就是事务(source、timer、observer
) -
获取
RunLoop
以及进一步验证RunLoop
和线程之间的关系- 获取主线程
runloop
:CFRunLoopGetMain()
发现底层就是调用
_CFRunLoopGet0
方法获取runloop
,入参是主线程 - 获取当前线程
runloop
:CFRunLoopGetCurrent()
同样的是调用了_CFRunLoopGet0
方法获取runloop
此时的入参是当前线程 - 获取
runloop
:_CFRunLoopGet0
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFSpinLock(&loopsLock); if (!__CFRunLoops) { //如果存储runloop的字典不存在则创建 //只有第一次进来的时候才会走到这 __CFSpinUnlock(&loopsLock); //创建一个临时字典 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); //创建主线程 CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); // dict : key value //将线程和runloop以key-value的形式存到临时字典 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); //OSAtomicCompareAndSwapPtrBarrier 将临时的字典写入到__CFRunLoops if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } //释放mainLoop CFRelease(mainLoop); __CFSpinLock(&loopsLock); } //通过线程也就是key 到__CFRunLoops中寻找runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFSpinUnlock(&loopsLock); if (!loop) { //如果没有找到则基于入参的线程创建一个runloop //这里只有可能是获取其他线程的runloop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { //此时将新创建的runloop存到__CFRunLoops中 CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); //赋值操作 loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); //释放新创建的runloop 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
的字典是否存在也就是__CFRunLoops
是否存在,如果不存在说明是第一次获取runloop
此时创建主线程runloop
然后存储到__CFRunLoops
中 - 通过
key
也就是入参的线程在__CFRunLoops
中查找runloop
如果找到了给对应的runloop
注册一个回调然后返回,如果没有找到则基于入参的线程创建一个新的runloop
然后存到__CFRunLoops
,然后一样的注册一个回调然后返回
总结:
从上述的源码发现底层runloop
的存储使用的是字典结构,线程是key
,对应的runloop
是value
,从这里进一步的印证了线程和 runloop的关系是一一对应的 - 先判断存储
- 获取主线程
-
RunLoop
创建
就是开辟空间创建runloop
对象并配置对应参数 -
RunLoop Mode
struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; Boolean _stopped; //mode是否被终止 char _padding[3]; //几种事件 CFMutableSetRef _sources0; //sources0 集合 CFMutableSetRef _sources1; //sources1 集合 CFMutableArrayRef _observers; //observers 集合 CFMutableArrayRef _timers; //timers 集合 CFMutableDictionaryRef _portToV1SourceMap; __CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中 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 */ }; 复制代码
从
__CFRunLoopMode
的结构体当中我们可以得到的消息是每个mode
中有若干个source0、source1、timer、observer
和若干个端口所以事务的执行是由mode
控制的,而上文张分析runloop
的结构的时候又知道每个runloop
当中都含有多个mode
,但是当前的mode
只有一个,所以从这里可以总结出两点:runloop
管理着mode
- 每次
runloop
运行的时候都必须指定有且仅有一个mode
指定对应的事务,如果需要执行其他mode
的事务需要切换mode
重新进入runloop
- 这里可以总结出
runloop
mode
事务、三者的关系如下图
Mode的类型
从官方文档中可以知道mode
的类型一共五种如下
其中mode在苹果文档中提及的有五个,而在iOS
中公开暴露出来的只有NSDefaultRunLoopMode
和NSRunLoopCommonModes
。NSRunLoopCommonModes
实际上是一个Mode
的集合,默认包括NSDefaultRunLoopMode
和NSEventTrackingRunLoopMode
(当以模态方式跟踪事件(例如鼠标拖动循环)时,应将运行循环设置为此模式。)。可通过CFRunLoopAddCommonMode
添加到runloop
运行模式集合中 -
RunLoop Source
Run Loop Source
分为Source、Observer、Timer
三种,也就是ModeItem
Source
source
又分为source0
和source1
表示可以唤醒RunLoop
的一些事件,例如用户点击了屏幕,就会创建一个RunLoop
source0
表示非系统事件,即用户自定义的事件,不能自动唤醒runloop
需要先调用CFRunLoopSourceSignal(source)
将source
置为待处理事件,然后再唤醒runloop
让其处理这个事件source1
由RunLoop和内核管理,source1带有mach_port_t,可以接收内核消息并触发回调,以下是source1的结构体
Observer
主要用于监听RunLoop的状态变化,并作出一定响应,主要有以下一些状态
Timer
就是是定时器,可以在设定的时间点抛出回调,
综上所述得知
Runloop
通过监控Source
来决定有没有任务要做,除此之外,我们还可以用Runloop Observer
来监控Runloop
本身的状态。Runloop Observer
可以监控上面的Runloop
事件,具体流程如下图。
-
添加
mode
CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)
添加一个mode
到runloop
的 commonmodes中
CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
返回当前运行的mode
的名称
从这里也可以证明当前运行的mode
是唯一的CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl)
返回runloop
中所有的mode
-
添加/移除
Source
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
添加source0
或者是source1
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) { //如果是kCFRunLoopCommonModes //则将对应的source添加到_commonModeItems中 CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; if (NULL == rl->_commonModeItems) { //如果没有则创建一个 rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); } // CFSetAddValue(rl->_commonModeItems, rls); if (NULL != set) { //如果set 存在则将source添加到每一个common-modes中去 //这里需要解释一下 应为kCFRunLoopCommonModes 是Mode的集合 所需需要在每个mode中都对应添加这个source //这样才能体现kCFRunLoopCommonModes的灵活性 不管设置那个mode 只要改mode在kCFRunLoopCommonModes这个 //集合中source就能执行 CFTypeRef context[2] = {rl, rls}; /* add new item to all common-modes */ CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context); CFRelease(set); } } else { //通过modeName 获取 mode CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true); if (NULL != rlm && NULL == rlm->_sources0) { //如果mode存在但是_sources0不存在则初始化_sources0、_sources1、_portToV1SourceMap 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)) { //如果_sources0和_sources1的集合中都不存在source if (0 == rls->_context.version0.version) { //如果version == 0 则添加到_sources0 CFSetAddValue(rlm->_sources0, rls); } else if (1 == rls->_context.version0.version) { //如果version == 1 则添加到_sources1 CFSetAddValue(rlm->_sources1, rls); __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info); if (CFPORT_NULL != src_port) { //此处只有在加到source1的时候才会把souce和一个mach_port_t对应起来 //可以理解为,source1可以通过内核向其端口发送消息来主动唤醒runloop CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls); __CFPortSetInsert(src_port, rlm->_portSet); } } __CFRunLoopSourceLock(rls); //把runloop加入到source的_runLoops中 if (NULL == rls->_runLoops) { rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops! } CFBagAddValue(rls->_runLoops, rl); __CFRunLoopSourceUnlock(rls); if (0 == rls->_context.version0.version) { 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 */ } } 复制代码
添加
source
步骤:- 先判断
mode
如果是commonMode
则将source
添加到_commonModeItems
中,并且将source
添加到每一个common-modes
中 - 如果不是
commonMode
则先通过mode
名称获取对应mode
- 判断
mode
的source0
是否存在,不存在则初始化_sources0、_sources1、_portToV1SourceMap
- 判断
source
是否已经在source0
或者是source1
的集合中 - 如果存在则啥都不干,不存在则判断
rls->_context.version0.version
是0,则添加到source0
集合中 - 是1 则添加到
source1
的集合中然后吧souce
和一个mach_port_t
对应起来
- 先判断
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */ CHECK_FOR_FORK(); Boolean doVer0Callout = false, doRLSRelease = false; __CFRunLoopLock(rl); //同样的是需要判断mode if (modeName == kCFRunLoopCommonModes) { if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) { CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; //_commonModeItems中删除对应source CFSetRemoveValue(rl->_commonModeItems, rls); if (NULL != set) { CFTypeRef context[2] = {rl, rls}; /* remove new item from all common-modes */ CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context); CFRelease(set); } } else { } } else { CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false); if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) { CFRetain(rls); if (1 == rls->_context.version0.version) { __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info); if (CFPORT_NULL != src_port) { CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port); __CFPortSetRemove(src_port, rlm->_portSet); } } CFSetRemoveValue(rlm->_sources0, rls); CFSetRemoveValue(rlm->_sources1, rls); __CFRunLoopSourceLock(rls); if (NULL != rls->_runLoops) { CFBagRemoveValue(rls->_runLoops, rl); } __CFRunLoopSourceUnlock(rls); if (0 == rls->_context.version0.version) { if (NULL != rls->_context.version0.cancel) { doVer0Callout = true; } } doRLSRelease = 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.cancel(rls->_context.version0.info, rl, modeName); /* CALLOUT */ } if (doRLSRelease) CFRelease(rls); } 复制代码
理解了添加的操作,删除的操作就比较简单了,这里就不赘述了
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
添加
Observer
和添加source
区别就在于Observer
如果已经添加到其他 runloop中去了则不能再被添加,从__CFRunLoopObserver
和__CFRunLoopSource
也可以看出差异点
定义的时候__CFRunLoopObserver
就是只能获取一个runloop
而__CFRunLoopSource
是一个runloop
的集合void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef * mode)
同CFRunLoopRemoveSource
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
同CFRunLoopAddObserver
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
同CFRunLoopRemoveSource
-
CFRunLoopRun
-
CFRunLoopRunInMode
指定模式下运行
runloop
再看CFRunLoopRunSpecific
方法SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); //根据modeName找到本次运行的mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); //如果没找到 || mode中没有注册任何事件,则就此停止,不进入循环 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); //取上一次运行的mode CFRunLoopModeRef previousMode = rl->_currentMode; //传入的mode置为当前mode rl->_currentMode = currentMode; int32_t result = kCFRunLoopRunFinished; // 1.通知observer即将进入runloop if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); //通知observer已退出runloop if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; } 复制代码
大致流程如下:
- 判断当前
runloop
中是否含有mode
如果没有则退出 - 判断
mode
中是否含有itme
没有则退出 - 将传入的
mode
置为当前运行mode
- 通知
Observer
进入runloop
- 启动
runloop
这里调用的是__CFRunLoopRun
方法 - 通知
Observer
已退出runloop
- 判断当前
-
__CFRunLoopRun
由于该源码太多这里就是用伪代码来代替
//核心函数 /* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){ //通过GCD开启一个定时器,然后开始跑圈 dispatch_source_t timeout_timer = NULL; ... dispatch_resume(timeout_timer); int32_t retVal = 0; //处理事务,即处理items do { // 通知 Observers: 即将处理timer事件 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 通知 Observers: 即将处理Source事件 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources) // 处理Blocks __CFRunLoopDoBlocks(rl, rlm); // 处理sources0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); // 处理sources0返回为YES if (sourceHandledThisLoop) { // 处理Blocks __CFRunLoopDoBlocks(rl, rlm); } // 判断有无端口消息(Source1) if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) { // 处理消息 goto handle_msg; } // 通知 Observers: 即将进入休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); // 等待被唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); // 通知 Observers: 被唤醒,结束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); handle_msg: if (被timer唤醒) { // 处理Timers __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()); }else if (被GCD唤醒){ // 处理gcd __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); }else if (被source1唤醒){ // 被Source1唤醒,处理Source1 __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) } // 处理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); return retVal; } 复制代码
发现和上文中的
runloop
流程图是一致的
这里挑一个处理timers源码作讲解其他大同小异__CFRunLoopDoTimers
源码
发现底层就是通过调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
方法来执行timer
的回调
这里也可以通过打印堆栈来验证
类似这种函数一共有6种:- block应用:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
- 调用timer:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- 响应source0:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- 响应source1:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
- GCD主队列:
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
- observer源:
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
- block应用:
-
总结
通过源码探索也可以发现
runloop
其实很简单,它是一个对象,并且和线程一一对应,主线程的runloop
是自动开启的,子线程的runloop
是需要手动开启的,每一个runloop
中会有多个mode
, 每一个mode
中又有多个source
,但是runloop
每次run
只能选择一种mode
(这也就造成了timer
和页面滑动之后的问题下文有详细讲解)。RunLoop
运行的核心是一个do..while..
循环,遍历所有需要处理的事件,如果有事件处理就让线程工作,没有事件处理则让线程休眠,同时等待事件到来。
-
-
RunLoop的应用及相关问题
-
NSTimer的应用
NSTimer
其实就是上文提到的timer
,底层runloop
会在每个时间点都注册一个事件,到了时间点回调时间,但是runloop
为了节省资源不会每次都很准确的回调事件,timer
有个属性是宽容度Tolerance
标识了可以有的多大误差,这样的机制就导致了timer
不是特别准确。而且如果某个时间点错过了就不会再重复执行
面试题:以+scheduledTimerWithTimeInterval
:的方式触发的timer
,在滑动页面上的列表时,timer会暂停回调, 为什么?如何解决?
应为scheduledTimerWithTimeInterval
添加方式默认的mode
是defaultMode
,而页面滑动时的mode
是UITrackingRunLoopMode
,滑动的时候runloop
会切换mode
,上文也提到过runloop
一次只能执行一个mode
中的事件,所以对应的timer
就会停止。
**解决方法是:**将timer
添加到NSRunLoopCommonModes
中就好 -
GCD Timer的应用
GCD
的线程管理是通过系统来直接管理的。GCD Timer
是通过dispatch port
给RunLoop
发送消息,来使RunLoop
执行相应的block
,如果所在线程没有RunLoop
,那么GCD
会临时创建一个线程去执行block
,执行完之后再销毁掉,因此GCD
的Timer
是可以不依赖RunLoop
的。
至于这两个Timer
的准确性问题,如果不在RunLoop
的线程里面执行,那么只能使用GCD Timer
,由于GCD Timer
是基于MKTimer(mach kernel timer)
,已经很底层了,因此是很准确的。
如果GCD Timer
在RunLoop
的线程中执行那么可能出现的问题和timer
大同小异
面试题:GCD Timer和 NSTimer那个更精确,为什么
这个问题不能单单直接肯定的说是GCD Timer
上文也分析到了如果在有RunLoop
的线程中精确度和NSTimer
差不多,具体原因可以看上文的NSTimer
分析
如果线程中没有runloop
首选GCD timer
,它相对于NSTimer
更加低层并且没有runloop
的影响所以更加精确 -
AutoreleasePool和RunLoop的关系
- App 启动后,苹果在主线程
RunLoop
里注册了两个Observer
,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。
- 第一个
Observer
监视的事件是Entry
(即将进入Loop
),其回调内会调用_objc_autoreleasePoolPush()
创建自动释放池。其order
是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。 - 第二个
Observer
监视了两个事件:BeforeWaiting
(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit
(即将退出Loop
) 时调用_objc_autoreleasePoolPop()
来释放自动释放池。这个Observer
的order
是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
总结一下自动释放池的创建、销毁时机:
kCFRunLoopEntry
进入runloop之前,创建一个自动释放池kCFRunLoopBeforeWaiting
休眠之前,销毁自动释放池,创建一个新的自动释放池kCFRunLoopExit
退出runloop之前,销毁自动释放池
- App 启动后,苹果在主线程
-
事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
-
手势识别
当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
-
GCD和RunLoop的关系
在RunLoop的源代码中可以看到用到了GCD的相关内容,但是RunLoop本身和GCD并没有直接的关系。当调用了dispatch_async(dispatch_get_main_queue(), <#^(void)block#>)时libDispatch会向主线程RunLoop发送消息唤醒RunLoop,RunLoop从消息中获取block,并且在__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__回调里执行这个block。不过这个操作仅限于主线程,其他线程dispatch操作是全部由libDispatch驱动的。
-
图片下载中RunLoop的应用
由于图片渲染到屏幕需要消耗较多资源,为了提高用户体验,当用户滚动Tableview的时候,只在后台下载图片,但是不显示图片,当用户停下来的时候才显示图片。
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
此方法即时此时正在滑动屏幕也不会出现卡顿的情况,应为滑动的过程中,runloop
的mode
是NSEventTrackingRunLoopMode
所以滑动的时候会切换mode
那么NSDefaultRunLoopMode
的事件也就停止了, 滑动完成之后才会切到NSDefaultRunLoopMode
然后继续事件,这样就不会造成卡顿现象 -
线程常驻
这个就很简单了其实就是开个线程将
runloop
跑起来,但是单纯的跑runloop
也不行,如果事件执行完了runloop
就会消亡对应的线程也就会销毁。所以可以添加一个timer
或者是NSPort
具体方法如下:[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
三种方法都能保证
runloop
运行,线程常驻
-
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
喜欢就支持一下吧