Runloop
什么是 Runloop脚粟?
從字面上講就是運(yùn)行循環(huán)覆旱。
它內(nèi)部就是do-while循環(huán),在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)核无。
一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop扣唱,主線程的RunLoop默認(rèn)已經(jīng)啟動(dòng),子線程的RunLoop得手動(dòng)啟動(dòng)(調(diào)用run方法)
RunLoop只能選擇一個(gè)Mode啟動(dòng),如果當(dāng)前Mode中沒有任何Source(Sources0噪沙、Sources1)炼彪、Timer,那么就直接退出RunLoop
基本的作用就是保持程序的持續(xù)運(yùn)行正歼,處理app中的各種事件辐马。通過runloop,有事運(yùn)行局义,沒事就休息喜爷,可以節(jié)省cpu資源,提高程序性能萄唇。
Runloop對(duì)象
iOS中有2套API來訪問和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表著RunLoop對(duì)象
NSRunLoop是基于CFRunLoopRef的一層OC包裝檩帐,所以要了解RunLoop內(nèi)部結(jié)構(gòu),需要多研究CFRunLoopRef層面的API另萤。
Runloop與線程
每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象
主線程的RunLoop已經(jīng)自動(dòng)創(chuàng)建好了轿塔,子線程的RunLoop需要主動(dòng)創(chuàng)建
RunLoop在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束時(shí)銷毀
獲得RunLoop對(duì)象
- Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
- Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
RunLoop相關(guān)類
Core Foundation中關(guān)于RunLoop的5個(gè)類
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop的運(yùn)行模式仲墨。
一個(gè)RunLoop包含若干個(gè)Mode勾缭,每個(gè)Mode又包含若干個(gè)(set)Source/(array)Timer/(array)Observer
每次RunLoop啟動(dòng)時(shí),只能指定其中一個(gè) Mode目养,這個(gè)Mode被稱作CurrentMode
如果需要切換Mode俩由,只能退出Loop,再重新指定一個(gè)Mode進(jìn)入
這樣做主要是為了分隔開不同組的Source/Timer/Observer癌蚁,讓其互不影響
mode主要是用來指定事件在運(yùn)行循環(huán)中的優(yōu)先級(jí)的幻梯,分為:
? NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默認(rèn),空閑狀態(tài)
? UITrackingRunLoopMode:ScrollView滑動(dòng)時(shí)會(huì)切換到該Mode
? UIInitializationRunLoopMode:run loop啟動(dòng)時(shí)努释,會(huì)切換到該mode
? NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
蘋果公開提供的Mode有兩個(gè):
? NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
? NSRunLoopCommonModes(kCFRunLoopCommonModes)
CFRunLoopTimerRef
CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器
CFRunLoopTimerRef基本上說的就是NSTimer碘梢,它受RunLoop的Mode影響
GCD的定時(shí)器不受RunLoop的Mode影響
CFRunLoopSourceRef
CFRunLoopSourceRef是事件源(輸入源)
按照官方文檔,Source的分類
Port-Based Sources
Custom Input Sources
Cocoa Perform Selector Sources
按照函數(shù)調(diào)用棧伐蒂,Source的分類
Source0:非基于Port的
Source1:基于Port的煞躬,通過內(nèi)核和其他線程通信,接收逸邦、分發(fā)系統(tǒng)事件
CFRunLoopObserverRef
CFRunLoopObserverRef是觀察者恩沛,能夠監(jiān)聽RunLoop的狀態(tài)改變
-
可以監(jiān)聽的時(shí)間點(diǎn)有以下幾個(gè)
kcfRunLoopEntry(即將進(jìn)入loop)//1
kcfRunLoopBeforeTimers(即將處理timer)//2
kcfRunLoopBeforeSources(即將處理source)//4
kcfRunLoopBeforeWaiting(即將進(jìn)入休眠)//32
kcfRunLoopAfterWaiting(剛從休眠中喚醒)//64
kcfRunLoopExit(即將退出loop)//128
添加Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----監(jiān)聽到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
});
// 添加觀察者:監(jiān)聽RunLoop的狀態(tài)
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 釋放Observer
CFRelease(observer);
RunLoop處理邏輯
通知Observer:即將進(jìn)入Loop(1)
通知Observer:將要處理Timer(2)
通知Observer:將要處理Source0(3)
處理Source0(4)
如果有Source0,跳到第9步(5)
通知Observer:線程即將休眠(6)
-
休眠缕减,等待喚醒:(7)
Source0(port)雷客。
timer啟動(dòng)
RunLoop設(shè)置的timer已經(jīng)超時(shí)
Runloop被外部手動(dòng)喚醒
通知Observer:線程將被喚醒(8)
-
處理未處理的時(shí)間(9)
如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器事件并重啟Runloop桥狡。進(jìn)入步驟2.
如果輸入源啟動(dòng)搅裙,傳遞相應(yīng)的消息皱卓。
如果RunLopp被顯式喚醒而且時(shí)間還沒超時(shí),重啟RunLoop部逮,進(jìn)入步驟2.
通知Observer:即將退出Loop
Runloop的應(yīng)用
- NSTimer
- ImageView顯示
- PerformSelector
- 常駐線程
- 自動(dòng)釋放池
runloop定時(shí)源和輸入源
image
Runloop處理的輸入事件有兩種不同的來源:輸入源(input source)和定時(shí)源(timer source)
輸入源傳遞異步消息娜汁,通常來自于其他線程或者程序。
定時(shí)源則傳遞同步消息甥啄,在特定時(shí)間或者一定的時(shí)間間隔發(fā)生
NSRunLoop的實(shí)現(xiàn)機(jī)制,及在多線程中如何使用
- 實(shí)現(xiàn)機(jī)制:回答runloop的基本作用,處理邏輯炬搭,前面都有蜈漓。
- 程序創(chuàng)建子線程的時(shí)候,才需要手動(dòng)啟動(dòng)runloop宫盔。主線程的runloop已經(jīng)默認(rèn)啟動(dòng)融虽。
- 在多線程中,你需要判斷是否需要runloop灼芭。如果需要runloop有额,那么你要負(fù)責(zé)配置runloop并啟動(dòng)。你不需要在任何情況下都去啟動(dòng)runloop彼绷。比如巍佑,你使用線程去處理一個(gè)預(yù)先定義好的耗時(shí)極長的任務(wù)時(shí),你就可以無需啟動(dòng)runloop寄悯。Runloop只在你要和線程有交互時(shí)才需要
runloop和線程有什么關(guān)系萤衰?
- 主線程的run loop默認(rèn)是啟動(dòng)的。
iOS的應(yīng)用程序里面猜旬,程序啟動(dòng)后會(huì)有一個(gè)如下的main()函數(shù)
( argc, * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, , NSStringFromClass([AppDelegate class]));
}
}
重點(diǎn)是UIApplicationMain()函數(shù)脆栋,這個(gè)方法會(huì)為main thread設(shè)置一個(gè)NSRunLoop對(duì)象,這就解釋了:為什么我們的應(yīng)用可以在無人操作的時(shí)候休息洒擦,需要讓它干活的時(shí)候又能立馬響應(yīng)椿争。
- 對(duì)其它線程來說,runloop默認(rèn)是沒有啟動(dòng)的熟嫩,runloop只在你要和線程有交互時(shí)才需要秦踪。
- 在任何一個(gè) Cocoa 程序的線程中,都可以通過以下代碼來獲取到當(dāng)前線程的 run loop 掸茅。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
autorelease 對(duì)象在什么情況下會(huì)被釋放洋侨?
- 分兩種情況:手動(dòng)干預(yù)釋放和系統(tǒng)自動(dòng)釋放
- 手動(dòng)干預(yù)釋放就是指定autoreleasepool,當(dāng)前作用域大括號(hào)結(jié)束就立即釋放
- 系統(tǒng)自動(dòng)去釋放:不手動(dòng)指定autoreleasepool,Autorelease對(duì)象會(huì)在當(dāng)前的 runloop 迭代結(jié)束時(shí)釋放
- kCFRunLoopEntry(1):第一次進(jìn)入會(huì)自動(dòng)創(chuàng)建一個(gè)autorelease
- kCFRunLoopBeforeWaiting(32):進(jìn)入休眠狀態(tài)前會(huì)自動(dòng)銷毀一個(gè)autorelease,然后重新創(chuàng)建一個(gè)新的autorelease
- kCFRunLoopExit(128):退出runloop時(shí)會(huì)自動(dòng)銷毀最后一個(gè)創(chuàng)建的autorelease
對(duì)于runloop的理解不正確的是
A 每一個(gè)線程都有其對(duì)應(yīng)的RunLoop
B 默認(rèn)非主線程的RunLoop是沒有運(yùn)行的
C 在一個(gè)單獨(dú)的線程中沒有必要去啟用RunLoop
D 可以將NSTimer添加到runloop中
- 參考答案:C
- 理由:說到RunLoop,它可是多線程的法定倦蚪。通常來說希坚,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完任務(wù)后就會(huì)退出線程陵且。但是裁僧,對(duì)于主線程是不能退出的个束,因此我們需要讓主線程即時(shí)任務(wù)執(zhí)行完畢,也可以繼續(xù)等待是接收事件而不退出聊疲,那么RunLoop就是關(guān)鍵法寶了茬底。但是非主線程通常來說就是為了執(zhí)行某一任務(wù)的,執(zhí)行完畢就需要?dú)w還資源获洲,因此默認(rèn)是不運(yùn)行RunLoop的阱表。NSRunLoop提供了一個(gè)添加NSTimer的方法,這個(gè)方法是在應(yīng)用正常狀態(tài)下會(huì)回調(diào)贡珊。
runloop的mode作用是什么最爬?
mode主要是用來指定事件在運(yùn)行循環(huán)中的優(yōu)先級(jí)的,分為:
? NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默認(rèn)门岔,空閑狀態(tài)
? UITrackingRunLoopMode:ScrollView滑動(dòng)時(shí)會(huì)切換到該Mode
? UIInitializationRunLoopMode:run loop啟動(dòng)時(shí)爱致,會(huì)切換到該mode
? NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
蘋果公開提供的Mode有兩個(gè):
? NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
? NSRunLoopCommonModes(kCFRunLoopCommonModes)
如果我們把一個(gè)NSTimer對(duì)象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)中的時(shí)候, ScrollView滾動(dòng)過程中會(huì)因?yàn)閙ode的切換,而導(dǎo)致NSTimer將不再被調(diào)度寒随。當(dāng)我們滾動(dòng)的時(shí)候糠悯,也希望不調(diào)度,那就應(yīng)該使用默認(rèn)模式妻往。但是互艾,如果希望在滾動(dòng)時(shí),定時(shí)器也要回調(diào)讯泣,那就應(yīng)該使用common mode忘朝。
請(qǐng)寫出NSTimer使用時(shí)的注意事項(xiàng)(兩項(xiàng)即可)
思路和上一題一樣,如果想要銷毀timer判帮,則必須先將timer置為失效局嘁,否則timer就一直占用內(nèi)存而不會(huì)釋放。造成邏輯上的內(nèi)存泄漏晦墙。該泄漏不能用xcode及instruments測(cè)出來悦昵。 另外對(duì)于要求必須銷毀timer的邏輯處理,未將timer置為失效晌畅,若每次都創(chuàng)建一次但指,則之前的不能得到釋放,則會(huì)同時(shí)存在多個(gè)timer的實(shí)例在內(nèi)存中抗楔。
參考答案:
? 注意timer添加到runloop時(shí)應(yīng)該設(shè)置為什么mode
? 注意timer在不需要時(shí)棋凳,一定要調(diào)用invalidate方法使定時(shí)器失效,否則得不到釋放
UITableViewCell上有個(gè)UILabel连躏,顯示NSTimer實(shí)現(xiàn)的秒表時(shí)間剩岳,手指滾動(dòng)cell過程中,label是否刷新入热,為什么拍棕?
和上一題一樣的思路晓铆,如果要cell滾動(dòng)過程中定時(shí)器正常回調(diào)绰播,UI正常刷新骄噪,那么要將timer放入到CommonModes下,因?yàn)槭荖SDefaultRunLoopMode蠢箩,只有在空閑狀態(tài)下才會(huì)回調(diào)链蕊。
為什么 UIScrollView 的滾動(dòng)會(huì)導(dǎo)致 NSTimer 失效?
- 思路和上一題一樣谬泌,解決辦法有2個(gè),一個(gè)是更改mode為NSRunLoopCommonModes(無論runloop運(yùn)行在哪個(gè)mode,都能運(yùn)行),還有種辦法是切換到主線程來更新UI界面的刷新
//將timer添加到NSDefaultRunLoopMode中
[NSTimer scheduledTimerWithTimeInterval: target: selector:@selector(timerTick:) userInfo: repeats:];
//然后再添加到NSRunLoopCommonModes里
NSTimer *timer = [NSTimer timerWithTimeInterval: target: selector:@selector(timerTick:) userInfo: repeats:];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
在滑動(dòng)頁面上的列表時(shí)滔韵,timer會(huì)暫定回調(diào),為什么呵萨?如何解決奏属?
- 思路和上一題一樣
在開發(fā)中如何使用RunLoop跨跨?什么應(yīng)用場景潮峦?
開啟一個(gè)常駐線程(讓一個(gè)子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)來消息勇婴,處理其他事件)
在子線程中開啟一個(gè)定時(shí)器
在子線程中進(jìn)行一些長期監(jiān)控
可以控制定時(shí)器在特定模式下執(zhí)行
可以讓某些事件(行為忱嘹、任務(wù))在特定模式下執(zhí)行
可以添加Observer監(jiān)聽RunLoop的狀態(tài),比如監(jiān)聽點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做一些事情)
文章如有問題耕渴,請(qǐng)留言拘悦,我將及時(shí)更正。
滿地打滾賣萌求贊橱脸,如果本文幫助到你础米,輕點(diǎn)下方的紅心,給作者君增加更新的動(dòng)力添诉。