不知道大家有沒(méi)有跟我一樣的困惑男旗?看過(guò)很多關(guān)于RunLoop的博客旦签,本來(lái)覺(jué)得已經(jīng)理解了runloop的運(yùn)行原理而咆,但是一寫(xiě)代碼就發(fā)現(xiàn)運(yùn)行結(jié)果和自己預(yù)想的不一致摇肌。我認(rèn)為有兩個(gè)原因:第一是沒(méi)有去認(rèn)真看runloop的源碼,第二是iOS封裝的NSRunLoop的三個(gè)接口媒役,run:/runUntilDate:/runMode:beforeDate:隱藏了一些細(xì)節(jié)祝谚,迷惑了大家。
關(guān)于RunLoop的運(yùn)行原理酣衷,有很多博客寫(xiě)的都很詳細(xì)交惯,我這里就不在重復(fù)了。我這篇文章從爬坑的角度來(lái)探究一下RunLoop. 大家都知道穿仪,NSRunLoop是對(duì)CFRunLoopRef的面向?qū)ο蟮姆庋b席爽。那么到底封裝了什么呢?
- CFRunLoopRef源碼探究
/* Reasons for CFRunLoopRunInMode() to Return */
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
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);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
上面是CFRunLoop最常用過(guò)的兩個(gè)函數(shù)啊片,這兩個(gè)函數(shù)最后都是調(diào)用CFRunLoopRunSpecific(),CFRunLoopRunSpecific()函數(shù)返回的是runloop結(jié)束的原因只锻,這個(gè)原因就是下面的枚舉,
enum {
kCFRunLoopRunFinished = 1, //runloop執(zhí)行完成紫谷,通常是source都被移除了
kCFRunLoopRunStopped = 2, //runloop被人為停止了
kCFRunLoopRunTimedOut = 3, //runloop超時(shí)了
kCFRunLoopRunHandledSource = 4 //runloop處理完source handle返回了
};
可以就看到一次runloop結(jié)束會(huì)又四種原因齐饮,kCFRunLoopRunFinished通常是runloop被釋放,或者source都被移除了笤昨;kCFRunLoopRunStopped是調(diào)用了CFRunLoopStop(); kCFRunLoopRunTimedOut是超過(guò)了設(shè)置的seconds祖驱,kCFRunLoopRunHandledSource取決于returnAfterSourceHandled參數(shù),如果returnAfterSourceHandled=true咬腋,那么runloop在被source觸發(fā)并執(zhí)行完了source 回調(diào)的函數(shù)后就會(huì)結(jié)束(如果是timer source且是repeats的羹膳,那么只有timer被invalidate才會(huì)算結(jié)束),這時(shí)結(jié)束原因就是kCFRunLoopRunHandledSource根竿。
理解了這些陵像,我們?cè)賮?lái)看NSRunLoop就簡(jiǎn)單多了!寇壳!
- [NSRunLoop run] 是不是就是CFRunLoopRun()?
[NSRunLoop run] 這個(gè)函數(shù)大家都很熟悉了醒颖,意思就是讓loop跑起來(lái),說(shuō)更詳細(xì)點(diǎn)壳炎,就是在NSDefaultRunLoopMode下泞歉,超時(shí)時(shí)間是[NSDate distantFuture]的條件下運(yùn)行runloop; 那么好像和CFRunLoopRun()是相同的操作, 我們來(lái)看看CFRunLoopRun()的源碼
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
從源代碼可以看到CFRunLoopRun()是在NSDefaultRunLoopMode下,超時(shí)時(shí)間是1.0e10, 而且returnAfterSourceHandled=false 的條件下運(yùn)行runloop. 看似和[NSRunLoop run]是一樣的匿辩。
但是在寫(xiě)代碼過(guò)程中我們發(fā)現(xiàn)腰耙,用[NSRunLoop run]運(yùn)行起來(lái)的runloop永遠(yuǎn)不會(huì)停止(除非我們移除了所有的timer source和input source),即使調(diào)用了CFRunLoopStop()也不會(huì)停止铲球,而CFRunLoopRun()的runloop在調(diào)用了CFRunLoopStop()后會(huì)立馬停止挺庞。根據(jù)observer看到runloop雖然不會(huì)退出,但是每次都會(huì)重新起一個(gè)runloop稼病,說(shuō)明returnAfterSourceHandled=true這說(shuō)明[NSRunLoop run]并不是直接調(diào)用的CFRunLoopRun()选侨,我們可以猜想掖鱼,他的實(shí)現(xiàn)代碼應(yīng)該是這樣的:
-(void) run(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, true);
CHECK_FOR_FORK();
} while (kCFRunLoopRunFinished != result);
}
也就是去掉了kCFRunLoopRunStopped != result這個(gè)條件。即使我們CFRunLoopStop()當(dāng)前runloop援制,do-while循環(huán)不會(huì)退出戏挡,又會(huì)重新開(kāi)始一個(gè)runloop。只有當(dāng)result==kCFRunLoopRunFinished的時(shí)候才會(huì)退出晨仑,從源碼中能看到褐墅,只有source為空了,或者runloop被dealloc的時(shí)候result才會(huì)等于kCFRunLoopRunFinished寻歧。那么可以得知掌栅,要想結(jié)束這種runloop只有移除所有的input source和timer source。這就和我們的實(shí)驗(yàn)結(jié)果是一致的了码泛,那么[NSRunLoop run]就理解清楚了猾封。同理[NSRunLoop runUntilDate]就是在run的基礎(chǔ)上可以設(shè)置超時(shí)時(shí)間外,其他都是一樣的噪珊。