一、runloop是什么?
從字面是理解就是循環(huán),無限循環(huán),app應(yīng)用從啟動(dòng)到退出,這個(gè)循環(huán)一直存在,啟動(dòng)就會(huì)自動(dòng)創(chuàng)建一個(gè)runloop,這也是app保持運(yùn)行不退出的關(guān)鍵所在。無限循環(huán)可以從CFRunloop的源碼可以看出:
?do{
///....
??}while(0== retVal);
二、runloop在干什么?
我們看下runloop的定義,在源碼中,runloop是一個(gè)結(jié)構(gòu)體,而結(jié)構(gòu)體中有一個(gè)重要的成員CFRunloopModelRef:

我們再看看CFRunloopModelRef的數(shù)據(jù)結(jié)構(gòu):

看到CFRunloopModelRef的結(jié)構(gòu)體的成員中有,source0,source1,observers,timer,port等成員。這個(gè)跟runloop是什么樣的關(guān)系呢?我們看看runloop的運(yùn)行原理了,蘋果官方有張圖:

其實(shí)NSRunLoop的本質(zhì)是一個(gè)消息機(jī)制的處理模式,runloop的運(yùn)行,其實(shí)就是不停的通過observer監(jiān)聽各種事件,包含各種source事件,timer,port等等,如果有這些事件,那就處理,沒有事件,就會(huì)進(jìn)入休眠,不停的重復(fù)上述過程。由此形成了運(yùn)行->檢測->休眠 ->運(yùn)行 的循環(huán)狀態(tài)。我們注意到source事件,timer,port,observer這些都是放在mode中,所以還有下面一種圖,才能完整描述runloop:

對于mode的類型,iOS系統(tǒng)給出了5種,分別是:
、、、
NSDefaultRunLoopMode ? ?// App默認(rèn)Mode通常主線程是在這個(gè)mode下運(yùn)行
UITrackingRunloopMode ? ?//界面跟蹤Mode用于scrollView追蹤觸摸界面滑動(dòng)時(shí)不受其他Mode影響
UIinitializationRunloopMode ? ?//在app一啟動(dòng)進(jìn)入的第一個(gè)Mode,啟動(dòng)完成后就不再使用
GSEventRecieveRunloopMode //蘋果使用繪圖相關(guān),系統(tǒng)內(nèi)核調(diào)用,開發(fā)者使用不到
NSRunLoopCommonModes //占位模式
、、、
開發(fā)者能使經(jīng)常使用的就三種模式(NSDefaultRunLoopMode、UITrackingRunloopMode、NSRunLoopCommonModes),runloop會(huì)根據(jù)事件在不同模式之間自動(dòng)切換,例如當(dāng)scrollView的滑動(dòng)事件,系統(tǒng)的主runloop會(huì)切換到UITrackingRunloopMode模式下,這是NSDefaultRunLoopMode中的事件就會(huì)停止,直到scrollView的滑動(dòng)停止,才會(huì)切換回NSDefaultRunLoopMode模式下。當(dāng)然還可自定義mode,并添加runloop中,這里就不深入講解了。
三、工作過程:
下圖是runloop的工作過程:

我們可以看下源碼的__CFRunLoopRun()函數(shù):
/// 用DefaultMode啟動(dòng)
void CFRunLoopRun(void) {
? ? CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動(dòng),允許設(shè)置RunLoop超時(shí)時(shí)間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
? ? return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的實(shí)現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
? ? /// 首先根據(jù)modeName找到對應(yīng)mode
? ? CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
? ? /// 如果mode里沒有source/timer/observer, 直接返回。
? ? if (__CFRunLoopModeIsEmpty(currentMode)) return;
? ? /// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。
? ? __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
? ? /// 內(nèi)部函數(shù),進(jìn)入loop
? ? __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
? ? ? ? Boolean sourceHandledThisLoop = NO;
? ? ? ? int retVal = 0;
? ? ? ? do {
? ? ? ? ? ? /// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
? ? ? ? ? ? __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
? ? ? ? ? ? /// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
? ? ? ? ? ? __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
? ? ? ? ? ? /// 執(zhí)行被加入的block
? ? ? ? ? ? __CFRunLoopDoBlocks(runloop, currentMode);
? ? ? ? ? ? /// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
? ? ? ? ? ? sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
? ? ? ? ? ? /// 執(zhí)行被加入的block
? ? ? ? ? ? __CFRunLoopDoBlocks(runloop, currentMode);
? ? ? ? ? ? /// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。
? ? ? ? ? ? if (__Source0DidDispatchPortLastTime) {
? ? ? ? ? ? ? ? Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
? ? ? ? ? ? ? ? if (hasMsg) goto handle_msg;
? ? ? ? ? ? }
? ? ? ? ? ? /// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
? ? ? ? ? ? if (!sourceHandledThisLoop) {
? ? ? ? ? ? ? ? __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
? ? ? ? ? ? }
? ? ? ? ? ? /// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
? ? ? ? ? ? /// ? 一個(gè)基于 port 的Source 的事件。
? ? ? ? ? ? /// ? 一個(gè) Timer 到時(shí)間了
? ? ? ? ? ? /// ? RunLoop 自身的超時(shí)時(shí)間到了
? ? ? ? ? ? /// ? 被其他什么調(diào)用者手動(dòng)喚醒
? ? ? ? ? ? __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
? ? ? ? ? ? ? ? mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
? ? ? ? ? ? }
? ? ? ? ? ? /// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
? ? ? ? ? ? __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
? ? ? ? ? ? /// 收到消息,處理消息。
? ? ? ? ? ? handle_msg:
? ? ? ? ? ? /// 9.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)。
? ? ? ? ? ? if (msg_is_timer) {
? ? ? ? ? ? ? ? __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
? ? ? ? ? ? }
? ? ? ? ? ? /// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。
? ? ? ? ? ? else if (msg_is_dispatch) {
? ? ? ? ? ? ? ? __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
? ? ? ? ? ? }
? ? ? ? ? ? /// 9.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件
? ? ? ? ? ? else {
? ? ? ? ? ? ? ? CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
? ? ? ? ? ? ? ? sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
? ? ? ? ? ? ? ? if (sourceHandledThisLoop) {
? ? ? ? ? ? ? ? ? ? mach_msg(reply, MACH_SEND_MSG, reply);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? /// 執(zhí)行加入到Loop的block
? ? ? ? ? ? __CFRunLoopDoBlocks(runloop, currentMode);
? ? ? ? ? ? if (sourceHandledThisLoop && stopAfterHandle) {
? ? ? ? ? ? ? ? /// 進(jìn)入loop時(shí)參數(shù)說處理完事件就返回。
? ? ? ? ? ? ? ? retVal = kCFRunLoopRunHandledSource;
? ? ? ? ? ? } else if (timeout) {
? ? ? ? ? ? ? ? /// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
? ? ? ? ? ? ? ? retVal = kCFRunLoopRunTimedOut;
? ? ? ? ? ? } else if (__CFRunLoopIsStopped(runloop)) {
? ? ? ? ? ? ? ? /// 被外部調(diào)用者強(qiáng)制停止了
? ? ? ? ? ? ? ? retVal = kCFRunLoopRunStopped;
? ? ? ? ? ? } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
? ? ? ? ? ? ? ? /// source/timer/observer一個(gè)都沒有了
? ? ? ? ? ? ? ? retVal = kCFRunLoopRunFinished;
? ? ? ? ? ? }
? ? ? ? ? ? /// 如果沒超時(shí),mode里沒空,loop也沒被停止,那繼續(xù)loop。
? ? ? ? } while (retVal == 0);
? ? }
? ? /// 10. 通知 Observers: RunLoop 即將退出。
? ? __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
四、runloop與線程的關(guān)系:
runloop與線程是一一對應(yīng)的關(guān)系,我應(yīng)用運(yùn)行的主線程對應(yīng)著主runloop,上面講了默認(rèn)就是開啟的,否則我們的應(yīng)用就不能保持運(yùn)行;而對于后臺(tái)線程,它所對應(yīng)的runloop則是沒有開啟的,這也就是后臺(tái)線程執(zhí)行完任務(wù)就會(huì)自動(dòng)銷毀回收的,如果如果需要?jiǎng)t手動(dòng)開啟。
蘋果開發(fā)的接口中并沒有直接創(chuàng)建Runloop的接口,如果需要使用Runloop通常CFRunLoopGetMain()和CFRunLoopGetCurrent()兩個(gè)方法來獲?。ㄍㄟ^上面的源代碼也可以看到,核心邏輯在_CFRunLoopGet_當(dāng)中),通過代碼并不難發(fā)現(xiàn)其實(shí)只有當(dāng)我們使用線程的方法主動(dòng)get?Runloop時(shí)才會(huì)在第一次創(chuàng)建該線程的Runloop,同時(shí)將它保存在全局的Dictionary中(線程和Runloop二者一一對應(yīng)),默認(rèn)情況下線程并不會(huì)創(chuàng)建Runloop(主線程的Runloop比較特殊,任何線程創(chuàng)建之前都會(huì)保證主線程已經(jīng)存在Runloop),同時(shí)在線程結(jié)束的時(shí)候也會(huì)銷毀對應(yīng)的Runloop。
五、runloop與autoReleasePool的關(guān)系:
AutoreleasePool是另一個(gè)與RunLoop相關(guān)討論較多的話題。其實(shí)從RunLoop源代碼分析,AutoreleasePool與RunLoop并沒有直接的關(guān)系,之所以將兩個(gè)話題放到一起討論最主要的原因是因?yàn)樵趇OS應(yīng)用啟動(dòng)后會(huì)注冊兩個(gè)Observer管理和維護(hù)AutoreleasePool。不妨在應(yīng)用程序剛剛啟動(dòng)時(shí)打印currentRunLoop可以看到系統(tǒng)默認(rèn)注冊了很多個(gè)Observer,其中有兩個(gè)Observer的callout都是**?_?wrapRunLoopWithAutoreleasePoolHandler**,這兩個(gè)是和自動(dòng)釋放池相關(guān)的兩個(gè)監(jiān)聽。
第一個(gè)Observer會(huì)監(jiān)聽RunLoop的進(jìn)入,它會(huì)回調(diào)objc_autoreleasePoolPush()向當(dāng)前的AutoreleasePoolPage增加一個(gè)哨兵對象標(biāo)志創(chuàng)建自動(dòng)釋放池。這個(gè)Observer的order是-2147483647優(yōu)先級最高,確保發(fā)生在所有回調(diào)操作之前。
第二個(gè)Observer會(huì)監(jiān)聽RunLoop的進(jìn)入休眠和即將退出RunLoop兩種狀態(tài),在即將進(jìn)入休眠時(shí)會(huì)調(diào)用objc_autoreleasePoolPop()?和?objc_autoreleasePoolPush()?根據(jù)情況從最新加入的對象一直往前清理直到遇到哨兵對象。而在即將退出RunLoop時(shí)會(huì)調(diào)用objc_autoreleasePoolPop()?釋放自動(dòng)自動(dòng)釋放池內(nèi)對象。這個(gè)Observer的order是2147483647,優(yōu)先級最低,確保發(fā)生在所有回調(diào)操作之后。
主線程的其他操作通常均在這個(gè)AutoreleasePool之內(nèi)(main函數(shù)中),以盡可能減少內(nèi)存維護(hù)操作(當(dāng)然你如果需要顯式釋放【例如循環(huán)】時(shí)可以自己創(chuàng)建AutoreleasePool否則一般不需要自己創(chuàng)建)。
在子線程你創(chuàng)建了 Pool 的話,產(chǎn)生的 Autorelease 對象就會(huì)交給 pool 去管理。如果你沒有創(chuàng)建 Pool ,但是產(chǎn)生了 Autorelease 對象,就會(huì)調(diào)用 autoreleaseNoPage 方法。在這個(gè)方法中,會(huì)自動(dòng)幫你創(chuàng)建一個(gè) hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage,如果你還是不理解,可以先看看 Autoreleasepool 的源代碼,再來看這個(gè)問題 ),并調(diào)用page->add(obj)將對象添加到 AutoreleasePoolPage 的棧中,也就是說你不進(jìn)行手動(dòng)的內(nèi)存管理,也不會(huì)內(nèi)存泄漏啦!我們看看autoreleaseNoPage的源碼:
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
? ? // No pool in place.
? ? // hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage。
? ? assert(!hotPage());
? ?// POOL_SENTINEL 只是 nil 的別名
? ? if(obj != POOL_SENTINEL? &&? DebugMissingPools) {
? ? ? ? // We are pushing an object with no pool in place,
? ? ? ? // and no-pool debugging was requested by environment.
? ? ? ? _objc_inform("MISSING POOLS: Object %p of class %s "
?? ? ? ? ? ? ? ? ? ? "autoreleased with no pool in place - "
?? ? ? ? ? ? ? ? ? ? "just leaking - break on "
?? ? ? ? ? ? ? ? ? ? "objc_autoreleaseNoPool() to debug",
?? ? ? ? ? ? ? ? ? ? (void*)obj, object_getClassName(obj));
? ? ? ? objc_autoreleaseNoPool(obj);
? ? ? ? returnnil;
? ? }
? ??// Install the first page.
? ? // 幫你創(chuàng)建一個(gè) hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage
? ? AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
? ? setHotPage(page);
??// Push an autorelease pool boundary if it wasn't already requested.
? ? // POOL_SENTINEL 只是 nil 的別名,哨兵對象
? ? if(obj != POOL_SENTINEL) {
? ? ? ? page->add(POOL_SENTINEL);
? ? }
? ??// Push the requested object.
? ? // 把對象添加到 自動(dòng)釋放池 進(jìn)行管理
? ? returnpage->add(obj);
}
六、runloop在開發(fā)中的應(yīng)用
1、線程常駐:
我們知道,讓一個(gè)后臺(tái)線程常駐,需要讓它對應(yīng)的runloop run起來,并且還要往里面添加一個(gè)timer或者一個(gè)port,不然線程執(zhí)行完任務(wù)就會(huì)立馬銷毀。

我們講線程對應(yīng)的runloop開啟,代碼執(zhí)行:

2、UITableView滑動(dòng)卡頓優(yōu)化
UITableView滑動(dòng)卡頓的原因一般主要有以下幾種:
1、每次重新計(jì)算cell布局和高度,導(dǎo)致計(jì)算量大;
2、cell上加載多張超大高清圖片,導(dǎo)致計(jì)算量大,渲染超時(shí);
3、動(dòng)態(tài)創(chuàng)建添加子視圖;
4、過多使用透明視圖,導(dǎo)致離屏渲染。
網(wǎng)上可以找到很多對應(yīng)的解決方案,這里就不多描述了,主要講下第2點(diǎn)的解決方案(runloop方案),思路如下:
tableView加載過多的高清大圖,Runloop不只處理iOS事件,渲染圖形也是runloop處理的。
? ? ? 而渲染圖形的UI操作必須在主線程中,不能開辟線程進(jìn)行圖形處理。
? ? ? 在拖動(dòng)tableView的時(shí)候,Runloop要處理拖動(dòng)事件,還要處理過多圖片渲染,而造成卡頓。
解決卡頓分析:
? ? ? 1、Runloop在一次循環(huán)渲染圖片過多,那就減少Runloop一次處理圖片的數(shù)量,最多一次三張;
? ? ? 2、將處理圖片的代碼放在block中,然后加入數(shù)組中,處理幾次加入幾次。
? ? ? 3、我們只需要渲染,tableView顯示的圖片,顯示圖片有最大個(gè)數(shù)。移開屏幕或者不處理的從隊(duì)列數(shù)組里刪去;
? ? ? 4、滑動(dòng)的時(shí)候,不處理渲染任務(wù);
代碼如下,具體見demo:
///保持runloop一直運(yùn)轉(zhuǎn)
-(NSTimer*)taskTimer{
? ? if(!_taskTimer) {
? ? ? ? _taskTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 repeats:YES block:^(NSTimer * _Nonnull timer) {
? ? ? ? }];
? ? }
? ? return _taskTimer;
}
///添加到任務(wù)隊(duì)列中
-(void)addTask:(RunloopTask)task{
? ? [self.tasksaddObject:task];
? ? if(self.tasks.count>MAXTASKS) {
? ? ? ? [self.tasks removeObjectAtIndex:0];
? ? }
}
#pragma mark 向runloop中注冊observer
- (void)addObserver{
? ? //拿到當(dāng)前的Runloop
? ? CFRunLoopRef runloop = CFRunLoopGetCurrent();
? ? CFRunLoopObserverContext context = {
? ? ? ? 0,
? ? ? ? (__bridgevoid*)(self),
? ? ? ? &CFRetain,
? ? ? ? &CFRelease,
? ? ? ? NULL
? ? };
? ??//定義一個(gè)觀察者,static內(nèi)存中只存在一個(gè)
? ? static CFRunLoopObserverRef obverser;
? ? //創(chuàng)建一個(gè)觀察者
? ? obverser =CFRunLoopObserverCreate(NULL, kCFRunLoopAfterWaiting, YES, 0, &callBack, &context);
? ? //添加觀察者?。。∧J(rèn)模式下,滑動(dòng)的時(shí)候不處理渲染任務(wù)
? ? CFRunLoopAddObserver(runloop, obverser, kCFRunLoopDefaultMode);
? ??//release
? ? CFRelease(obverser);
}
///runloop即將休眠的observer回調(diào)
void?callBack (CFRunLoopObserverRef observer,CFRunLoopActivity activity,void*info){
? ? HXWRunloopTask*runloopTask = (__bridgeHXWRunloopTask*)info;
? ? if(runloopTask.tasks.count==0){
? ? ? ? return;
? ? }
?? ?///從任務(wù)隊(duì)列中去任務(wù)執(zhí)行
? ? RunloopTask task = runloopTask.tasks.firstObject;
? ? task();
? ? [runloopTask.tasks removeObjectAtIndex:0];
}
runloop應(yīng)用很廣泛,比如還有NSTimer的,需要添加到runloop中才能有效,GCD的異步提交到主隊(duì)列,上面提到過的自動(dòng)釋放池等等,在這里就不一一敘述了。
以上,歡迎各位指正。