2017年07月12日20:48:41又學(xué)習(xí)
一基本作用
1.保持程序的持續(xù)執(zhí)行
2.處理 app 中的各種事件(觸摸、定時(shí)器時(shí)間、Selector事件)
3.節(jié)省 CPU 資源 , 提高程序性能, 該做事的時(shí)候做事, 該休息的時(shí)候休息
二什么是 runloop
1.運(yùn)行循環(huán)
2.其實(shí)它內(nèi)部就是 do-while 循環(huán), 在這個(gè)循環(huán)內(nèi)部不斷處理各種任務(wù)(比如說(shuō) source, Timer 和 Observer)
3.一個(gè)線程對(duì)應(yīng)一個(gè) runloop, 主線程的 Runloop默認(rèn)是開(kāi)啟的, 子線程的 Runloop 必須手動(dòng)開(kāi)啟, 調(diào)用 run 方法
4.Runloop 只能選擇一個(gè) Model 啟動(dòng), 如果當(dāng)前的 model 中沒(méi)有任何的 source, Timer 和 Observer,那么直接退出 runloop
三自動(dòng)釋放池什么時(shí)候釋放?
在 runloop 睡眠之前(KCFRunloopBeforeWaiting)
四在開(kāi)發(fā)中如何使用 Runloop? 什么應(yīng)用場(chǎng)景?
1.開(kāi)啟一個(gè)常駐線程(讓子線程不進(jìn)入消亡狀態(tài), 等待其他線程發(fā)來(lái)消息, 處理其他事件)
1.1在子線程中開(kāi)啟一個(gè)定時(shí)器,
1.2在子線程中進(jìn)行一些長(zhǎng)期監(jiān)控
2.可以控制定時(shí)器在特定的 Mode 下執(zhí)行
3.可以讓某些事件(行為和任務(wù))在特定的 model 下執(zhí)行
4.可以添加 Observer 監(jiān)聽(tīng) Runloop 的狀態(tài), 比如監(jiān)聽(tīng)點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做的一件事情)
0. runLoop的概念
- 1.運(yùn)行在一個(gè)Thread上的一個(gè)do-while 死循環(huán),保證程序持續(xù)運(yùn)行
- 2.這個(gè)循環(huán)專(zhuān)門(mén)用來(lái)接收事件源,通知 綁定的線程 去執(zhí)行這個(gè)事件.
- 3.主線程默認(rèn)開(kāi)啟事件接收循環(huán),并自動(dòng)創(chuàng)建 自動(dòng)釋放池
- 4.一般來(lái)講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線程能隨時(shí)處理事件但并不退出,
- 5.主線程默認(rèn)開(kāi)啟,自己手動(dòng)創(chuàng)建的子線程的RunLoop默認(rèn)沒(méi)有開(kāi)啟,需要我們自己開(kāi)啟

這種模型通常被稱(chēng)作 Event Loop。 Event Loop 在很多系統(tǒng)和框架里都有實(shí)現(xiàn),所以,RunLoop 實(shí)際上就是一個(gè)對(duì)象,這個(gè)對(duì)象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來(lái)執(zhí)行上面 Event Loop 的邏輯。線程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。
OSX/iOS 系統(tǒng)中,提供了兩個(gè)這樣的對(duì)象:NSRunLoop 和 CFRunLoopRef。
- CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的,它提供了純 C 函數(shù)的 API,所有這些 API 都是線程安全的。
- NSRunLoop 是基于 CFRunLoopRef 的封裝,提供了面向?qū)ο蟮?API,但是這些 API 不是線程安全的。CFRunLoopRef 的代碼是開(kāi)源的,你可以在這里 http://opensource.apple.com/tarballs/CF/ 下載到整個(gè) CoreFoundation 的源碼來(lái)查看。
1. RunLoop基本作用
- 保持程序的持續(xù)運(yùn)行
- 處理App中的各種事件(比如觸摸事件、定時(shí)器事件、Selector事件)
- 節(jié)省CPU資源,提高程序性能:該做事時(shí)做事,該休息時(shí)休息
2. 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í)銷(xiāo)毀.
線程和 RunLoop 之間是一一對(duì)應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。
線程剛創(chuàng)建時(shí)并沒(méi)有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷(xiāo)毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)。
3.獲得RunLoop對(duì)象
#######1.Foundation oc的NSRunloop
[NSRunLoop currentRunLoop]; // 創(chuàng)建當(dāng)前子線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
默認(rèn)不允許創(chuàng)建runloop,提供了兩個(gè)獲取的方法
#######2.Core Foundation C的runloop
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
- NSRunLoop是基于CFRunLoopRef的一層OC包裝,所以要了解RunLoop內(nèi)部結(jié)構(gòu),需要多研究CFRunLoopRef層面的API(Core Foundation層面)
4. RunLoop相關(guān)類(lèi)
Core Foundation中關(guān)于RunLoop的5個(gè)類(lèi)
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

runLoop.png
5. RunLoop的運(yùn)行模式
一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè)Mode又包含若干個(gè)Source/Timer/Observer
每次RunLoop啟動(dòng)時(shí),只能指定其中一個(gè) Mode,這個(gè)Mode被稱(chēng)作 CurrentMode
如果需要切換Mode,只能退出Loop,再重新指定一個(gè)Mode進(jìn)入
這樣做主要是為了分隔開(kāi)不同組的Source/Timer/Observer,讓其互不影響
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
- kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
- UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
- UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
- kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode,不是一種真正的Mode
6. CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器
CFRunLoopTimerRef 是基于時(shí)間的觸發(fā)器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào)(函數(shù)指針)。當(dāng)其加入到 RunLoop 時(shí),RunLoop會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)。
- 1.基本上說(shuō)的就是NSTimer,它會(huì)受到runloop的mode的影響
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- 2.GCD的定時(shí)器不受Runloop的mode的影響

7. CFRunLoopSourceRef是事件源(輸入源)
Source0:非基于Port的,用于用戶主動(dòng)觸發(fā)的事件
Source1:基于Port的,通過(guò)內(nèi)核和其它線程相互發(fā)送消息
8. CFRunLoopObserverRef
在runloop中每一個(gè)runLoop對(duì)象同一個(gè)時(shí)間內(nèi), 對(duì)應(yīng)一個(gè)mode, 當(dāng)選中的mode中沒(méi)有timer或者source時(shí) runloop就會(huì)被銷(xiāo)毀.CFRunLoopObserverRef是觀察者,能夠監(jiān)聽(tīng)RunLoop的狀態(tài)改變
8.1 監(jiān)聽(tīng)的時(shí)間點(diǎn)
可以監(jiān)聽(tīng)的時(shí)間點(diǎn)有以下幾個(gè)
可以監(jiān)聽(tīng)的時(shí)間點(diǎn)有以下幾個(gè)
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 1 kCFRunLoopBeforeTimers = (1UL << 1), // 2 kCFRunLoopBeforeSources = (1UL << 2), // 4 kCFRunLoopBeforeWaiting = (1UL << 5), // 32 kCFRunLoopAfterWaiting = (1UL << 6), // 64 kCFRunLoopExit = (1UL << 7), // 128 kCFRunLoopAllActivities = 0x0FFFFFFFU};
上面的 Source/Timer/Observer 被統(tǒng)稱(chēng)為 mode item,一個(gè) item 可以被同時(shí)加入多個(gè) mode。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的。如果一個(gè) mode 中一個(gè) item 都沒(méi)有,則 RunLoop 會(huì)直接退出,不進(jìn)入循環(huán)。
8.2 Observer的使用
添加Observer添加觀察者:監(jiān)聽(tīng)RunLoop的狀態(tài)
釋放Observer
// 創(chuàng)建observerCFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"----監(jiān)聽(tīng)到RunLoop狀態(tài)發(fā)生改變---%zd", activity);});// 添加觀察者:監(jiān)聽(tīng)RunLoop的狀態(tài)CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);// 釋放ObserverCFRelease(observer);
9. RunLoop 的內(nèi)部邏輯

runLoop時(shí)間隊(duì)列.png

runloop.png
可以看到,實(shí)際上 RunLoop 就是這樣一個(gè)函數(shù),其內(nèi)部是一個(gè) do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)里;直到超時(shí)或被手動(dòng)停止,該函數(shù)才會(huì)返回。




10. RunLoop應(yīng)用
10.1 應(yīng)用 NSTimer:
鑒于我們的NSTimer默認(rèn)工作在Default模式下, 當(dāng)我們tableView滾動(dòng)的時(shí)候, 會(huì)切換到tracking模式下,這樣定時(shí)器不在工作, 直到回到default模式下, 會(huì)恢復(fù)工作,這樣造成定時(shí)器停掉, 我們可以將定時(shí)器放在common這個(gè)標(biāo)記下, common默認(rèn)標(biāo)記default和tracking模式,這樣就可以正常工作了, 但是NStimer定時(shí)器也不是很準(zhǔn)確, 推薦使用GCD的Timer

10.2 ImageView顯示: PerformSelector
我們可以在設(shè)置下載圖片的任務(wù)放在default模型下,但我們的tableView滾動(dòng)的時(shí)候, 就不在下載數(shù)據(jù), 這樣的話, 就可以保證tableView的流暢性.

那么該performSelector事件,只會(huì)當(dāng)主線程RunLoop處于default時(shí),才會(huì)被執(zhí)行。如果不這么做,可能造成當(dāng)TableView滾動(dòng)時(shí),又去下載圖片可能造成卡頓。(不過(guò)其實(shí)一般的下載操作,但是放在其他子線程完成的,這里只是舉個(gè)簡(jiǎn)單的例子).

10.3 常駐線程
應(yīng)用場(chǎng)景:經(jīng)常在后臺(tái)進(jìn)行耗時(shí)操作,如:監(jiān)控聯(lián)網(wǎng)狀態(tài),掃描沙盒等 不希望線程處理完事件就銷(xiāo)毀,保持常駐狀態(tài)
第一種(推薦)
- 開(kāi)啟
- 退出
第二種(奇葩法)
優(yōu)點(diǎn):退出RunLoop比較方便-定義個(gè)標(biāo)記 while(flag){...}
1 創(chuàng)建一個(gè)單例NSThread對(duì)象,并start
2 NSThread線程入口函數(shù)中完成:創(chuàng)建@autoreleasepool{ … }然后開(kāi)啟Thread的RunLoop
添加一個(gè)NSMachPort給Thread對(duì)象來(lái)防止runloop執(zhí)行完畢之后會(huì)退出注意,不要講NSMachPort對(duì)象發(fā)送給其他子線程我看到有一種使用while(1){ 開(kāi)啟RunLoop }這樣的結(jié)構(gòu)來(lái)防止runloop退出,雖然是可以,個(gè)人覺(jué)得太粗糙3 然后給單例NSThread的runloop上注冊(cè) Timer/Sources/Observer
4 完成回調(diào)業(yè)務(wù)處理
使用port或是自定義的input source來(lái)和其他線程進(jìn)行通信 (AFN就是例子)
10.3.1 AFN在NSURLConnection中的使用
AFN在NSURLConnection中的使用. 其希望能在后臺(tái)線程接收 Delegate 回調(diào)。為此 AFNetworking 單獨(dú)創(chuàng)建了一個(gè)線程,并在這個(gè)線程中啟動(dòng)了一個(gè) RunLoop:

RunLoop 啟動(dòng)前內(nèi)部必須要有至少一個(gè)Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個(gè)新的 NSMachPort 添加進(jìn)去了。通常情況下,調(diào)用者需要持有這個(gè) NSMachPort (mach_port) 并在外部線程通過(guò)這個(gè) port 發(fā)送消息到 loop 內(nèi);但此處添加 port 只是為了讓 RunLoop 不至于退出,并沒(méi)有用于實(shí)際的發(fā)送消息。

當(dāng)需要這個(gè)后臺(tái)線程執(zhí)行任務(wù)時(shí),AFNetworking 通過(guò)調(diào)用 [NSObject performSelector:onThread:..] 將這個(gè)任務(wù)扔到了后臺(tái)線程的 RunLoop 中。
10.3.2 AFN在NSURLSession中的使用

AFN使用RunLoop的實(shí)例.png

Snip20160719_6.png
- 自動(dòng)釋放池: AutoreleasePool
在主線程即將休眠時(shí),釋放自動(dòng)釋放池在主線程即將喚醒時(shí),再次創(chuàng)建自動(dòng)釋放池,并將之前的對(duì)象再次放入池中
kCFRunLoopEntry >>> 創(chuàng)建自動(dòng)釋放池kCFRunLoopBeforeWaiting >>> 重建自動(dòng)釋放池kCFRunLoopExit >>> 銷(xiāo)毀自動(dòng)釋放池
特別注意:
在啟動(dòng)RunLoop之前建議用 @autoreleasepool {...}包裹
意義:創(chuàng)建一個(gè)大釋放池,釋放{}期間創(chuàng)建的臨時(shí)對(duì)象,一般好的框架的作者都會(huì)這么做
題外話:
以后為了增加用戶體驗(yàn) 在用戶UI交互的時(shí)候 不做事件處理 我們可以把需要做的操作放到NSDefaultRunLoopMode
補(bǔ)充:GCD定時(shí)器
一般的NSTimer定時(shí)器因?yàn)槭艿絉unLoop,會(huì)存在時(shí)間不準(zhǔn)時(shí)的情況.
上文有提到GCD不受RunLoop影響,下面簡(jiǎn)單的說(shuō)一下它的使用
11. RunLoop面試題
11.1 什么是RunLoop?
- 從字面意思看:運(yùn)行循環(huán)、跑圈.其實(shí)它內(nèi)部就是do-while循環(huán),在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)(比如Source、Timer、Observer
) - 一個(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中沒(méi)有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop
11.2 自動(dòng)釋放池什么時(shí)候釋放?
通過(guò)Observer監(jiān)聽(tīng)RunLoop的狀態(tài)在主線程即將休眠時(shí),釋放自動(dòng)釋放池在主線程即將喚醒時(shí),再次創(chuàng)建自動(dòng)釋放池,并將之前的對(duì)象再次放入池中
11.3 在開(kāi)發(fā)中如何使用RunLoop?什么應(yīng)用場(chǎng)景?
- 1.開(kāi)啟一個(gè)常駐線程(讓一個(gè)子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)來(lái)消息,處理其他事件)
1.在子線程中開(kāi)啟一個(gè)定時(shí)器
2.在子線程中進(jìn)行一些長(zhǎng)期監(jiān)控
- 2.可以控制定時(shí)器在特定模式下執(zhí)行
- 3.可以讓某些事件(行為、任務(wù))在特定模式下行
- 4.可以添加Observer監(jiān)聽(tīng)RunLoop的狀態(tài),比如監(jiān)聽(tīng)點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做一些事情)
11.4 NSRunLoop的實(shí)現(xiàn)機(jī)制,及在多線程中如何使用
NSRunLoop是IOS消息機(jī)制的處理模式
1.NSRunLoop的主要作用:控制NSRunLoop里面線程的執(zhí)行和休眠,在有事情做的時(shí)候使當(dāng)前NSRunLoop控制的線程工作,沒(méi)有事情做讓當(dāng)前NSRunLoop的控制的線程休眠。
2.NSRunLoop 就是一直在循環(huán)檢測(cè),從線程start到線程end,檢測(cè)inputsource(如點(diǎn)擊,雙擊等操作)異步事件,檢測(cè)timesource同步事件,檢測(cè)到輸入源會(huì)執(zhí)行處理函數(shù),首先會(huì)產(chǎn)生通知,corefunction向線程添加runloop observers來(lái)監(jiān)聽(tīng)事件,意在監(jiān)聽(tīng)事件發(fā)生時(shí)來(lái)做處理。
3.runloopmode是一個(gè)集合,包括監(jiān)聽(tīng):事件源,定時(shí)器,以及需通知的runloop observers
只有在為你的程序創(chuàng)建次線程的時(shí)候,才需要運(yùn)行runloop。對(duì)于程序的主線程而言,run loop是關(guān)鍵部分。Cocoa提供了運(yùn)行主線程run loop的代碼同時(shí)也會(huì)自動(dòng)運(yùn)行run loop。IOS程序UIApplication中的run方法在程序正常啟動(dòng)的時(shí)候就會(huì)啟動(dòng)run loop。如果你使用xcode提供的模板創(chuàng)建的程序,那你永遠(yuǎn)不需要自己去啟動(dòng)run loop
在多線程中,你需要判斷是否需要run loop。如果需要run loop,那么你要負(fù)責(zé)配置run loop并啟動(dòng)。你不需要在任何情況下都去啟動(dòng)run loop。比如,你使用線程去處理一個(gè)預(yù)先定義好的耗時(shí)極長(zhǎng)的任務(wù)時(shí),你就可以毋需啟動(dòng)run loop。Run loop只在你要和線程有交互時(shí)才需要
文@greedyDoor/greedyDoor(簡(jiǎn)書(shū)作者)原文鏈接:http://m.itdecent.cn/p/8240f8f1525f著作權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),并標(biāo)注“簡(jiǎn)書(shū)作者”。