RunLoop
源码下载地址RunLoop
其实就是一种事务处理的循环,是事件接受、分发的机制的实现,用来不停的调度工做以及处理输入事件。其本质就是一个 do-while
循环,若是有输入则执行对应事件,没有则休息,这里RunLoop
的do-while
和普通的do-while
循环不同,普通的循环会让cpu
处于忙等待的状况,当cpu
跑满了也就崩溃了。而runloop
的do-while
循环是一种闲等待,也就是不会消耗cpu
因此runloop
具备休眠功能。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
对象并配置对应参数数组
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、source一、timer、observer
和若干个端口因此事务的执行是由 mode
控制的,而上文张分析 runloop
的结构的时候又知道每一个 runloop
当中都含有多个 mode
,可是当前的 mode
只有一个,因此从这里能够总结出两点:markdown
runloop
管理着 mode
runloop
运行的时候都必须指定有且仅有一个 mode
指定对应的事务,若是须要执行其余 mode
的事务须要切换 mode
从新进入 runloop
runloop
mode
事务、三者的关系以下图
Mode的类型
从官方文档中能够知道 mode
的类型一共五种以下 其中mode在苹果文档中说起的有五个,而在
iOS
中公开暴露出来的只有 NSDefaultRunLoopMode
和 NSRunLoopCommonModes
。 NSRunLoopCommonModes
其实是一个 Mode
的集合,默认包括 NSDefaultRunLoopMode
和 NSEventTrackingRunLoopMode
(当以模态方式跟踪事件(例如鼠标拖动循环)时,应将运行循环设置为此模式。)。可经过 CFRunLoopAddCommonMode
添加到 runloop
运行模式集合中app
Run Loop Source
分为Source、Observer、Timer
三种,也就是 ModeItem
async
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
事件,具体流程以下图。 ide
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、_sources一、_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、_sources一、_portToV1SourceMap
source
是否已经在 source0
或者是 source1
的集合中 rls->_context.version0.version
是0,则添加到 source0
集合中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
指定模式下运行 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;
}
复制代码
大体流程以下:oop
runloop
中是否含有 mode
若是没有则退出mode
中是否含有 itme
没有则退出mode
置为当前运行 mode
Observer
进入 runloop
runloop
这里调用的是 __CFRunLoopRun
方法Observer
已退出 runloop
因为该源码太多这里就是用伪代码来代替源码分析
//核心函数
/* 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种:性能
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
经过源码探索也能够发现 runloop
其实很简单,它是一个对象,而且和线程一一对应,主线程的 runloop
是自动开启的,子线程的 runloop
是须要手动开启的,每个 runloop
中会有多个 mode
, 每个 mode
中又有多个 source
,可是 runloop
每次 run
只能选择一种 mode
(这也就形成了 timer
和页面滑动以后的问题下文有详细讲解)。
RunLoop
运行的核心是一个do..while..
循环,遍历全部须要处理的事件,若是有事件处理就让线程工做,没有事件处理则让线程休眠,同时等待事件到来。
NSTimer
其实就是上文提到的 timer
,底层 runloop
会在每一个时间点都注册一个事件,到了时间点回调时间,可是 runloop
为了节省资源不会每次都很准确的回调事件,timer
有个属性是宽容度 Tolerance
标识了能够有的多大偏差,这样的机制就致使了 timer
不是特别准确。并且若是某个时间点错过了就不会再重复执行
面试题:以+scheduledTimerWithTimeInterval
:的方式触发的timer
,在滑动页面上的列表时,timer会暂停回调, 为何?如何解决?
应为scheduledTimerWithTimeInterval
添加方式默认的 mode
是 defaultMode
,而页面滑动时的 mode
是 UITrackingRunLoopMode
,滑动的时候 runloop
会切换 mode
,上文也提到过 runloop
一次只能执行一个 mode
中的事件,因此对应的 timer
就会中止。
**解决方法是:**将 timer
添加到 NSRunLoopCommonModes
中就好
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
的影响因此更加精确
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以前,销毁自动释放池苹果注册了一个 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的回调。
在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驱动的。
因为图片渲染到屏幕须要消耗较多资源,为了提升用户体验,当用户滚动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
运行,线程常驻