關(guān)于 runloop 面試中經(jīng)常被問到:
講講 RunLoop,項(xiàng)目中有用到嗎付枫?
RunLoop內(nèi)部實(shí)現(xiàn)邏輯抱完?
Runloop和線程的關(guān)系?
timer 與 Runloop 的關(guān)系饵沧?
程序中添加每3秒響應(yīng)一次的NSTimer,當(dāng)拖動tableview時timer可能無法響應(yīng)要怎么解決赌躺?
Runloop 是怎么響應(yīng)用戶操作的狼牺, 具體流程是什么樣的?
說說RunLoop的幾種狀態(tài)礼患?
Runloop的mode作用是什么是钥?
一. RunLoop簡介
運(yùn)行循環(huán),在程序運(yùn)行過程中循環(huán)做一些事情缅叠,如果沒有Runloop程序執(zhí)行完畢就會立即退出悄泥,如果有Runloop程序會一直運(yùn)行,并且時時刻刻在等待用戶的輸入操作肤粱。RunLoop可以在需要的時候自己跑起來運(yùn)行弹囚,在沒有操作的時候就停下來休息。充分節(jié)省CPU資源领曼,提高程序性能鸥鹉。
定義
RunLoop的實(shí)質(zhì)是一個死循環(huán),用于保證程序的持續(xù)運(yùn)行庶骄,只有當(dāng)程序退出的時候才會結(jié)束(由main函數(shù)開啟主線程的RunLoop)作用
保持程序的持續(xù)運(yùn)行
處理App中的各種事件(觸摸毁渗、定時器、Selector事件)
節(jié)省CPU資源单刁,提高程序性能(該做事做事祝蝠,沒事做休息)獲取方法
使用NSRunLoop(面向?qū)ο螅┗蛘逤FRunLoopRef(底層C語言)
在任何一個Cocoa程序的線程中,都可以通過:
NSRunLoop *runloop = [NSRunLoopcurrentRunLoop];
來獲取到當(dāng)前線程的run loop幻碱。原理
RunLoop開啟一個循環(huán)事件绎狭,并接受輸入事件,接受的事件來自兩種不同的來源:
1.輸入源(input source)(傳遞異步事件)
2.定時源(timer source)(傳遞同步事件)
RunLoop接收到消息后采用handlePort褥傍、customSrc儡嘶、mySelector和timerFired等四個方法處理對應(yīng)的事件
當(dāng)RunLoop沒有接收到消息時,則進(jìn)入休眠狀態(tài)恍风,以保持程序持續(xù)運(yùn)行蹦狂。應(yīng)用范疇:
1.定時器(Timer)
2.PerformSelector
3.GCD Async Main Queue
4.事件響應(yīng)誓篱、手勢識別、界面刷新
5.網(wǎng)絡(luò)請求 √ AutoreleasePoolRunLoop在實(shí)際開中的應(yīng)用
1.控制線程生命周期(線程笨ǎ活)
2.解決NSTimer在滑動時停止工作的問題
3.監(jiān)控應(yīng)用卡頓
4.性能優(yōu)化運(yùn)行邏輯
01窜骄、通知Observers:進(jìn)入Loop 02、通知Observers:即將處理Timers 03摆屯、通知Observers:即將處理Sources 04邻遏、處理Blocks 05、處理Source0(可能會再次處理Blocks) 06虐骑、如果存在Source1准验,就跳轉(zhuǎn)到第8步 07、通知Observers:開始休眠(等待消息喚醒) 08廷没、通知Observers:結(jié)束休眠(被某個消息喚醒) 01> 處理Timer 02> 處理GCD Async To Main Queue 03> 處理Source1 09糊饱、處理Blocks 10、根據(jù)前面的執(zhí)行結(jié)果颠黎,決定如何操作 01> 回到第02步 02> 退出Loop 11另锋、通知Observers:退出LoopRunLoop的結(jié)構(gòu)組成
RunLoop位于蘋果的Core Foundation庫中,而Core Foundation庫則位于iOS架構(gòu)分層的Core Service層中(值得注意的是狭归,Core Foundation是一個跨平臺的通用庫砰蠢,不僅支持Mac,iOS唉铜,同時也支持Windows):
- 六個被調(diào)起方法
主線程 (有 RunLoop 的線程) 幾乎所有函數(shù)都從以下六個之一的函數(shù)調(diào)起:
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRunloop is calling out to an abserver callback function
用于向外部報(bào)告 RunLoop 當(dāng)前狀態(tài)的更改,框架中很多機(jī)制都由 RunLoopObserver 觸發(fā)律杠,如 CAAnimation
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRunloop is calling out to a block
消息通知潭流、非延遲的perform、dispatch調(diào)用柜去、block回調(diào)灰嫉、KVO
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
CFRunloop is servicing the main desipatch queue
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRunloop is calling out to a timer callback function
延遲的perform, 延遲dispatch調(diào)用
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
CFRunloop is calling out to a source 0 perform function
處理App內(nèi)部事件、App自己負(fù)責(zé)管理(觸發(fā))嗓奢,如UIEvent讼撒、CFSocket。普通函數(shù)調(diào)用股耽,系統(tǒng)調(diào)用
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRunloop is calling out to a source 1 perform function
由RunLoop和內(nèi)核管理根盒,Mach port驅(qū)動,如CFMachPort物蝙、CFMessagePort
二. RunLoop基本作用
保持程序持續(xù)運(yùn)行炎滞,程序一啟動就會開一個主線程,主線程一開起來就會跑一個主線程對應(yīng)的RunLoop,RunLoop保證主線程不會被銷毀诬乞,也就保證了程序的持續(xù)運(yùn)行
處理App中的各種事件(比如:觸摸事件册赛,定時器事件钠导,Selector事件等)
節(jié)省CPU資源,提高程序性能森瘪,程序運(yùn)行起來時牡属,當(dāng)什么操作都沒有做的時候,RunLoop就告訴CUP扼睬,現(xiàn)在沒有事情做逮栅,我要去休息,這時CUP就會將其資源釋放出來去做其他的事情痰驱,當(dāng)有事情做的時候RunLoop就會立馬起來去做事情
我們先通過API內(nèi)一張圖片來簡單看一下RunLoop內(nèi)部運(yùn)行原理
通過圖片可以看出证芭,RunLoop在跑圈過程中,當(dāng)接收到Input sources 或者 Timer sources時就會交給對應(yīng)的處理方去處理担映。當(dāng)沒有事件消息傳入的時候废士,RunLoop就休息了。這里只是簡單的理解一下這張圖蝇完,接下來我們來了解RunLoop對象和其一些相關(guān)類官硝,來更深入的理解RunLoop運(yùn)行流程。
三. RunLoop在哪里開啟
UIApplicationMain函數(shù)內(nèi)啟動了Runloop短蜕,程序不會馬上退出氢架,而是保持運(yùn)行狀態(tài)。因此每一個應(yīng)用必須要有一個runloop朋魔,
我們知道主線程一開起來岖研,就會跑一個和主線程對應(yīng)的RunLoop,那么RunLoop一定是在程序的入口main函數(shù)中開啟警检。
進(jìn)入UIApplicationMain
我們發(fā)現(xiàn)它返回的是一個int數(shù)孙援,那么我們對main函數(shù)做一些修改
運(yùn)行程序,我們發(fā)現(xiàn)只會打印開始扇雕,并不會打印結(jié)束拓售,這說明在UIApplicationMain函數(shù)中,開啟了一個和主線程相關(guān)的RunLoop镶奉,導(dǎo)致UIApplicationMain不會返回础淤,一直在運(yùn)行中,也就保證了程序的持續(xù)運(yùn)行哨苛。
我們來看到RunLoop的源碼
// 用DefaultMode啟動
voidCFRunLoopRun(void) {/* DOES CALLOUT */
int32_t result;
do{
result =CFRunLoopRunSpecific(CFRunLoopGetCurrent(),kCFRunLoopDefaultMode,1.0e10,false); CHECK_FOR_FORK();
}while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
我們發(fā)現(xiàn)RunLoop確實(shí)是do while通過判斷result的值實(shí)現(xiàn)的鸽凶。因此,我們可以把RunLoop看成一個死循環(huán)建峭。如果沒有RunLoop吱瘩,UIApplicationMain函數(shù)執(zhí)行完畢之后將直接返回,也就沒有程序持續(xù)運(yùn)行一說了迹缀。
四. RunLoop對象
Fundation框架 (基于CFRunLoopRef的封裝)
NSRunLoop對象
CoreFoundation
CFRunLoopRef對象
因?yàn)镕undation框架是基于CFRunLoopRef的一層OC封裝使碾,這里我們主要研究CFRunLoopRef源碼
如何獲得RunLoop對象
Foundation[NSRunLoopcurrentRunLoop];// 獲得當(dāng)前線程的RunLoop對象
[NSRunLoopmainRunLoop];// 獲得主線程的RunLoop對象Core
FoundationCFRunLoopGetCurrent();// 獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetMain();// 獲得主線程的RunLoop對象
RunLoop接收幾種輸入源蜜徽,系統(tǒng)默認(rèn)定義了幾種模式?
- 輸入源有兩種
基于端口的輸入源(port)
自定義的輸入源(custom) - 系統(tǒng)定義的RunLoop模式有五種
最常用的有三種票摇,如下所示:
1.NSDefaultRunLoopMode
默認(rèn)模式拘鞋,主線程中默認(rèn)是NSDefaultRunLoopMode
2.UITrackingRunLoopMode
視圖滾動模式,RunLoop會處于該模式下
3.NSRunLoopCommonModes
并不是真正意義上的Mode矢门,是一個占位用的“Mode”盆色,默認(rèn)包含了NSDefaultRunLoopMode和UITrackingRunLoopMode兩種模式
RunLoop模式的原理和使用注意點(diǎn)?
原理和注意點(diǎn)
- 一個RunLoop包含若干個Mode祟剔,每個Mode又包含若干個Source隔躲、Observer、Timer(如下圖所示)
- 每次RunLoop啟動物延,只能指定一個Mode宣旱,這個Mode被稱為CurrentMode
- 如果需要切換Mode,只能退出Loop叛薯,再重新指定一個Mode進(jìn)入浑吟, 以使不同組之間的Source、Observer耗溜、Timer互不受影響
在 CoreFoundation 里面關(guān)于 RunLoop 有5個類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
其中 CFRunLoopModeRef 類并沒有對外暴露组力,只是通過 CFRunLoopRef 的接口進(jìn)行了封裝
一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer抖拴。每次調(diào)用 RunLoop 的主函數(shù)時燎字,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode阿宅。如果需要切換 Mode候衍,只能退出 Loop,再重新指定一個 Mode 進(jìn)入家夺。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響伐弹。
CFRunLoopSourceRef 是事件產(chǎn)生的地方拉馋。Source有兩個版本:Source0 和 Source1。
? Source0 只包含了一個回調(diào)(函數(shù)指針)惨好,它并不能主動觸發(fā)事件煌茴。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source)日川,將這個 Source 標(biāo)記為待處理蔓腐,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件龄句。
? Source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針)回论,被用于通過內(nèi)核和其他線程相互發(fā)送消息散罕。這種 Source 能主動喚醒 RunLoop 的線程,其原理在下面會講到傀蓉。
CFRunLoopTimerRef 是基于時間的觸發(fā)器欧漱,它和 NSTimer 是toll-free bridged 的,可以混用葬燎。其包含一個時間長度和一個回調(diào)(函數(shù)指針)误甚。當(dāng)其加入到 RunLoop 時,RunLoop會注冊對應(yīng)的時間點(diǎn)谱净,當(dāng)時間點(diǎn)到時窑邦,RunLoop會被喚醒以執(zhí)行那個回調(diào)。
CFRunLoopObserverRef 是觀察者壕探,每個 Observer 都包含了一個回調(diào)(函數(shù)指針)冈钦,當(dāng) RunLoop 的狀態(tài)發(fā)生變化時,觀察者就能通過回調(diào)接受到這個變化浩蓉∨杉蹋可以觀測的時間點(diǎn)有以下幾個:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
上面的 Source/Timer/Observer 被統(tǒng)稱為 mode item,一個 item 可以被同時加入多個 mode捻艳。但一個 item 被重復(fù)加入同一個 mode 時是不會有效果的驾窟。如果一個 mode 中一個 item 都沒有,則 RunLoop 會直接退出认轨,不進(jìn)入循環(huán)绅络。
通過上面分析我們知道,CFRunLoopModeRef代表RunLoop的運(yùn)行模式嘁字,一個RunLoop包含若干個Mode恩急,每個Mode又包含若干個Source0/Source1/Timer/Observer,而RunLoop啟動時只能選擇其中一個Mode作為currentMode纪蜒。
Source1/Source0/Timers/Observer分別代表什么
Source1 : 基于Port的線程間通信
Source0 : 觸摸事件衷恭,PerformSelectors
我們通過代碼驗(yàn)證一下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"點(diǎn)擊了屏幕");
}
打斷點(diǎn)之后打印堆棧信息,當(dāng)xcode工具區(qū)打印的堆棧信息不全時纯续,可以在控制臺通過“bt”指令打印完整的堆棧信息随珠,由堆棧信息中可以發(fā)現(xiàn),觸摸事件確實(shí)是會觸發(fā)Source0事件猬错。
同樣的方式驗(yàn)證performSelector堆棧信息
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
});
可以發(fā)現(xiàn)PerformSelectors同樣是觸發(fā)Source0事件
- Timers : 定時器窗看,NSTimer
通過代碼驗(yàn)證
[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"NSTimer ---- timer調(diào)用了");
}];
打印完整堆棧信息
- Observer : 監(jiān)聽器,用于監(jiān)聽RunLoop的狀態(tài)
Source
即可以喚醒Runloop
的一些事件倦炒。比如用戶點(diǎn)擊了屏幕显沈,就會創(chuàng)建一個input source。
-
source0
: 非系統(tǒng)事件
只包含了一個回調(diào)(函數(shù)指針)逢唤,它并不能主動觸發(fā)事件拉讯。使用時涤浇,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理遂唧,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop芙代,讓其處理這個事件。
-
source1
: 系統(tǒng)事件
包含了一個 mach_port和一個回調(diào)(函數(shù)指針)盖彭,被用于通過內(nèi)核和其他線程相互發(fā)送消息纹烹。這種 Source 能主動喚醒 RunLoop 的線程
Timer
我們經(jīng)常用的NSTimer
就屬于這一類。
Observer
某個observer可以監(jiān)聽runloop
的狀態(tài)變化召边,并作出一定反應(yīng)铺呵。
RunLoop運(yùn)行流程
RunLoop 結(jié)構(gòu)組成
RunLoop位于蘋果的Core Foundation庫中,而Core Foundation庫則位于iOS架構(gòu)分層的Core Service層中(值得注意的是隧熙,Core Foundation是一個跨平臺的通用庫片挂,不僅支持Mac,iOS贞盯,同時也支持Windows):
五. RunLoop和線程間的關(guān)系
每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
RunLoop保存在一個全局的Dictionary里音念,線程作為key,RunLoop作為value
主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
RunLoop在第一次獲取時創(chuàng)建躏敢,在線程結(jié)束時銷毀
通過源碼查看上述對應(yīng)
// 拿到當(dāng)前Runloop 調(diào)用_CFRunLoopGet0CFRunLoopRefCFRunLoopGetCurrent(void) { CHECK_FOR_FORK();
CFRunLoopRefrl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if(rl)returnrl;return_CFRunLoopGet0(pthread_self());
}
// 查看_CFRunLoopGet0方法內(nèi)部CF_EXPORTCFRunLoopRef_CFRunLoopGet0(pthread_t t) {if(pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);if(!__CFRunLoops) { __CFUnlock(&loopsLock);CFMutableDictionaryRefdict =CFDictionaryCreateMutable(kCFAllocatorSystemDefault,0,NULL, &kCFTypeDictionaryValueCallBacks);
// 根據(jù)傳入的主線程獲取主線程對應(yīng)的RunLoop
CFRunLoopRefmainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主線程 將主線程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if(!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void*volatile*)&__CFRunLoops)) {CFRelease(dict);
}CFRelease(mainLoop); __CFLock(&loopsLock);
}
// 從字典里面拿闷愤,將線程作為key從字典里獲取一個loop
CFRunLoopRefloop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果loop為空,則創(chuàng)建一個新的loop件余,所以runloop會在第一次獲取的時候創(chuàng)建
if(!loop) {
CFRunLoopRefnewLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 創(chuàng)建好之后讥脐,以線程為key runloop為value,一對一存儲在字典中啼器,下次獲取的時候旬渠,則直接返回字典內(nèi)的runloop
if(!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if(pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void*)loop,NULL);
if(0== _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void*)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void(*)(void*))__CFFinalizeRunLoop);
}
}
returnloop;
}
從上面的代碼可以看出,線程和 RunLoop 之間是一一對應(yīng)的端壳,其關(guān)系是保存在一個 Dictionary 里告丢。所以我們創(chuàng)建子線程RunLoop時,只需在子線程中獲取當(dāng)前線程的RunLoop對象即可[NSRunLoop currentRunLoop];如果不獲取损谦,那子線程就不會創(chuàng)建與之相關(guān)聯(lián)的RunLoop岖免,并且只能在一個線程的內(nèi)部獲取其 RunLoop
[NSRunLoop currentRunLoop];方法調(diào)用時,會先看一下字典里有沒有存子線程相對用的RunLoop成翩,如果有則直接返回RunLoop觅捆,如果沒有則會創(chuàng)建一個赦役,并將與之對應(yīng)的子線程存入字典中麻敌。當(dāng)線程結(jié)束時,RunLoop會被銷毀掂摔。
NSTimer和RunLoop的關(guān)系术羔?
- NSTimer需要添加到Runloop中赢赊, 才能執(zhí)行的情況
NSTimer *timer = [NSTimer timerWithTimeInterval:1.f target:self selector:@selector(update) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- NSTimer默認(rèn)被添加到Runloop中, 直接執(zhí)行的情況
[NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(update) userInfo:nil repeats:YES];
NSTimer準(zhǔn)確嗎级历,如果不準(zhǔn)確释移,如何設(shè)計(jì)一個準(zhǔn)確的timer?
不準(zhǔn)確
準(zhǔn)確的Timer應(yīng)該和當(dāng)前線程的RunLoopMode保持一致
TableView/ScrollView/CollectionView滾動時為什么NSTimer會停止寥殖?
一個RunLoop不能同時共存兩個mode
當(dāng)滾動視圖滾動時玩讳,當(dāng)前RunLoop處于UITrackingRunLoopMode,
NSTimer的RunLoopMode和當(dāng)前線程的RunLoopMode不一致嚼贡,所以會停止
解決方法:將timer的runloopMode改為UITrackingRunLoopMode或者NSRunLoopCommonModes
如果NSTimer在分線程中創(chuàng)建熏纯,會發(fā)生什么,應(yīng)該注意什么粤策?
- NSTimer沒有啟動
-- 在主線程中樟澜,系統(tǒng)默認(rèn)創(chuàng)建并啟動主線程的runloop
-- 在分線程中,系統(tǒng)不會自動啟動runloop叮盘,需要手動啟動 - 解決方法:
啟動分線程的runLoop
在異步線程中下載很多圖片,如果失敗了,該如何處理?請結(jié)合RunLoop來談?wù)劷鉀Q方案
在異步線程中啟動一個RunLoop重新發(fā)送網(wǎng)絡(luò)請求,下載圖片
如果程序啟動就需要執(zhí)行一個耗時操作秩贰,你會怎么做?
開啟一個異步的子線程柔吼,并啟動它的RunLoop來執(zhí)行該耗時操作
runloop與autoreleasepool的關(guān)系,如果在分線程中啟動一個異步請求毒费,會有什么問題?
判斷其是否請求結(jié)束嚷堡,如果未結(jié)束蝗罗,要保持當(dāng)前線程一直啟動,直到結(jié)束
while(!isFinish)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
可以看到蝌戒,實(shí)際上 RunLoop 就是這樣一個函數(shù)串塑,其內(nèi)部是一個 do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時北苟,線程就會一直停留在這個循環(huán)里桩匪;直到超時或被手動停止,該函數(shù)才會返回友鼻。
程序啟動時傻昙,runloop是如何工作的?如果程序啟動就需要執(zhí)行一個耗時操作彩扔,你會怎么做妆档?
程序啟動時,系統(tǒng)默認(rèn)創(chuàng)建并啟動主線程的runloop虫碉,runloop會默認(rèn)創(chuàng)建兩個Observe來進(jìn)行監(jiān)聽runloop的進(jìn)出和睡眠贾惦,有事情的時候就去做,沒事的休眠。
(線程(創(chuàng)建)-->runloop將進(jìn)入-->最高優(yōu)先級OB創(chuàng)建釋放池-->runloop將睡-->最低優(yōu)先級OB銷毀舊池創(chuàng)建新池-->runloop將退出-->最低優(yōu)先級OB銷毀新池-->線程(銷毀))
線程剛創(chuàng)建時并沒有runloop须板,如果你不主動去獲取碰镜,那么一直都不會有。
耗時操作可以放在分線程中進(jìn)行习瑰,結(jié)束后回到主線程绪颖。
經(jīng)典面試題
Runloop和線程是什么關(guān)系?
每條線程都有唯一的一個與之對應(yīng)的RunLoop對象甜奄,其關(guān)系是保存在一個全局的 Dictionary 里柠横;主線程的RunLoop已經(jīng)自動創(chuàng)建,子線程的RunLoop需要主動創(chuàng)建课兄;RunLoop在第一次獲取時創(chuàng)建滓鸠,在線程結(jié)束時銷毀
Runloop的mode作用是什么?
指定事件在運(yùn)行循環(huán)中的優(yōu)先級的第喳,
線程的運(yùn)行需要不同的模式糜俗,去響應(yīng)各種不同的事件,去處理不同情境模式曲饱。(比如可以優(yōu)化tableview的時候可以設(shè)置UITrackingRunLoopMode下不進(jìn)行一些操作悠抹,比如設(shè)置圖片等。)
以+scheduledTimerWithTimeInterval:的方式觸發(fā)的timer扩淀,在滑動頁面上的列表時楔敌,timer會暫停回調(diào)驻谆, 為什么卵凑?
滑動scrollView時,主線程的RunLoop會切換到UITrackingRunLoopMode這個Mode胜臊,執(zhí)行的也是UITrackingRunLoopMode下的任務(wù)(Mode中的item)勺卢,而timer是添加在NSDefaultRunLoopMode下的,所以timer任務(wù)并不會執(zhí)行象对,只有當(dāng)UITrackingRunLoopMode的任務(wù)執(zhí)行完畢黑忱,runloop切換到NSDefaultRunLoopMode后,才會繼續(xù)執(zhí)行timer勒魔。
如何解決在滑動頁面上的列表時甫煞,timer會暫停回調(diào)冠绢?
將Timer放到NSRunLoopCommonModes中執(zhí)行即可
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; [[NSRunLoop currentRunLoop] run];復(fù)制代碼
NSTImer使用時需要注意什么抚吠?
注意timer添加到runloop時應(yīng)該設(shè)置為什么mode
注意timer在不需要時,一定要調(diào)用invalidate方法使定時器失效弟胀,否則得不到釋放
RunLoop 有哪些應(yīng)用楷力?
常駐內(nèi)存蕊玷、AutoreleasePool 自動釋放池
AutoreleasePool 和 RunLoop 有什么聯(lián)系?
iOS應(yīng)用啟動后會注冊兩個 Observer 管理和維護(hù) AutoreleasePool弥雹。應(yīng)用程序剛剛啟動時默認(rèn)注冊了很多個Observer,其中有兩個Observer的 callout 都是 _ wrapRunLoopWithAutoreleasePoolHandler延届,這兩個是和自動釋放池相關(guān)的兩個監(jiān)聽剪勿。
第一個 Observer 會監(jiān)聽 RunLoop 的進(jìn)入,它會回調(diào)objc_autoreleasePoolPush() 向當(dāng)前的 AutoreleasePoolPage 增加一個哨兵對象標(biāo)志創(chuàng)建自動釋放池方庭。這個 Observer 的 order 是 -2147483647 優(yōu)先級最高厕吉,確保發(fā)生在所有回調(diào)操作之前。
第二個 Observer 會監(jiān)聽 RunLoop 的進(jìn)入休眠和即將退出 RunLoop 兩種狀態(tài)械念,在即將進(jìn)入休眠時會調(diào)用 objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 根據(jù)情況從最新加入的對象一直往前清理直到遇到哨兵對象头朱。而在即將退出 RunLoop 時會調(diào)用objc_autoreleasePoolPop() 釋放自動自動釋放池內(nèi)對象。這個Observer 的 order 是 2147483647 龄减,優(yōu)先級最低项钮,確保發(fā)生在所有回調(diào)操作之后。
NSRunLoop 和 CFRunLoopRef 區(qū)別
CFRunLoopRef 基于C 線程安全希停,NSRunLoop 基于 CFRunLoopRef 面向?qū)ο蟮腁PI 是不安全的