02Runloop深入學(xué)習(xí)

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)啟
Paste_Image.png

這種模型通常被稱(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ì)象:NSRunLoopCFRunLoopRef

  • 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:

  1. kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
  2. UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
  3. UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
  4. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
  5. 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的影響
Paste_Image.png
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ì)返回。

Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png

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

Paste_Image.png
10.2 ImageView顯示: PerformSelector

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

Paste_Image.png

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

延遲執(zhí)行.png
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)
第一種(推薦)

  1. 開(kāi)啟
  2. 退出

第二種(奇葩法)
優(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:

Paste_Image.png

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ā)送消息。

Paste_Image.png

當(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

  1. 只有在為你的程序創(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

  2. 在多線程中,你需要判斷是否需要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ū)作者”。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Run loop 剖析:Runloop 接收的輸入事件來(lái)自兩種不同的源:輸入源(intput source)和定時(shí)...
    Mitchell閱讀 12,655評(píng)論 17 111
  • 什么是Run Loops RunLoops是與線程相關(guān)聯(lián)的基礎(chǔ)部分,一個(gè)Run Loop就是事件處理循環(huán),他是用來(lái)...
    傻傻小蘿卜閱讀 1,111評(píng)論 0 5
  • RunLoop的定義與概念RunLoop的主要作用main函數(shù)中的RunLoopRunLoop與線程的關(guān)系RunL...
    __silhouette閱讀 1,078評(píng)論 0 6
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,561評(píng)論 0 13
  • 我問(wèn)鳥(niǎo),你在學(xué)校也這么作嗎? 鳥(niǎo)說(shuō)。嗯。 我好好奇,怎么作的?老師會(huì)說(shuō)你嗎? 鳥(niǎo)說(shuō),會(huì)。 我問(wèn),那老師怎么說(shuō)的? ...
    S貓閱讀 451評(píng)論 6 8

友情鏈接更多精彩內(nèi)容