iOS開發(fā)經(jīng)驗(18)-Runloop

目錄

  1. Runloop
  2. RunLoop 與線程
  3. 個人理解總結(jié)
  4. 應(yīng)用場景
1. 什么是RunLoop

基本作用

  • 保持程序的持續(xù)運行(do-while循環(huán)秧均,使app不斷運行)
  • 處理App中的各種事件(觸摸、定時器辈末、Selector)
  • 節(jié)省CPU資源晒喷、提高程序性能:該做事的時候做事孝偎,該休息的時候休息。

RunLoop基本運行流程
運行邏輯總結(jié):一個線程對應(yīng)一個runLoop,主線程的runloop是程序一啟動,默認就創(chuàng)建一個runloop,創(chuàng)建好了之后就會給它添加一些默認的模式,每個模式里面會有很多的 source /timer/observer ,添加好這些模式后,observer就會監(jiān)聽主線程的runloop,進入runloop后,就開始處理事件,先處理timer,再處理source0,source0處理完之后再處理source1,當把這些所有的事件反復(fù)的處理完之后,如果沒有事件了,那么runloop就會進入睡眠狀態(tài),當用戶又觸發(fā)了新的事件,就會喚醒runloop,喚醒runloop后回到第二步,重新處理新的timer,新的source0,新的source1,處理完后就睡眠,一直反復(fù),當我們把程序關(guān)閉或者強退,這個時候observer就會監(jiān)聽都runloop退出了凉敲。
簡單說就是:
先進入 RunLoop衣盾,處理系統(tǒng)默認事件寺旺,觸發(fā)事件的時候,RunLoop 醒來處理 timer势决、source0阻塑、source1,處理完再睡覺果复。

運行循環(huán)本質(zhì)
線程在執(zhí)行中的休眠和激活就是由RunLoop對象進行管理的
Runloop 輪詢用來響應(yīng)事件陈莽,runloop里的任務(wù)串行執(zhí)行,容易受堵塞

main 函數(shù)中的 RunLoop
UIApplicationMain函數(shù)內(nèi)部就啟動了一個RunLoop虽抄,所以UIApplicationMain 函數(shù)一直沒有返回走搁,保持了程序的持續(xù)運行。這個默認啟動的 RunLoop 是跟主線程相關(guān)聯(lián)的

2. RunLoop 與線程

一條線程對應(yīng)一個 RunLoop迈窟,主線程的 RunLoop 只要程序已啟動就會默認創(chuàng)建并與主線程綁定好私植,RunLoop 底層的實現(xiàn)是通過字典的形式來將 線程 和 RunLoop 來綁定的,RunLoop 可以理解為懶加載菠隆,子線程的 RunLoop 可以調(diào)用 currentRunLoop兵琳,先從字典里面根據(jù)子線程取,如果沒有就會去創(chuàng)建并與子線程綁定骇径,保存到字典當中躯肌。每個 RunLoop 里面有很多的 Mode,每個 Mode 里面又有很多的source破衔、timer清女、observer。RunLoop 在同一時刻只能執(zhí)行一種 Mode晰筛,當執(zhí)行這種 Mode 的時候嫡丙,只有這種 Mode 中的source、timer读第、observer 有效曙博,別的 Mode 無效,這樣做是為了避免邏輯的混亂怜瞒。

  • 每條線程都有唯一的一個與之對應(yīng)的 RunLoop 對象
  • 主線程的 RunLoop 自動創(chuàng)建好了父泳,子線程的 RunLoop 需要主動創(chuàng)建
  • RunLoop 在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀
3. 獲取RunLoop 對象

Foundation

//獲得當前線程的 RunLoop 對象
[NSRunLoop currentRunLoop];
//獲得主線程的 RunLoop 對象
[NSRunLoop mainRunLoop];

Core Foundation

//當前RunLoop
CFRunLoopGetCurrent();
//主線程 RunLoop
CFRunLoopGetMain();

源:分為輸入源和定時源吴汪。必須將至少其中一個添加到Runloop中惠窄,才能保證Runloop不立即退出;當你創(chuàng)建輸入源的時候漾橙,需要將其分配給 runloop 中的一個或多個模式杆融;模式只會在特定事件影響監(jiān)聽的源。

  • 輸入源:
    • 自定義輸入源-source0 用戶操作觸摸事件源霜运。使用回調(diào)函數(shù)來配置自定義輸入源
    • 基于端口的輸入源-source1 接受分發(fā)系統(tǒng)事件脾歇。不需要直接創(chuàng)建輸入源蒋腮。只要簡單的創(chuàng)建對象,并使用 NSPort 的方法將該端口添加到 Ruhnloop 中
  • 定時源:產(chǎn)生基于時間的通知介劫,但它并不是實時機制徽惋。和輸入源一樣,定時器也和 runloop 的特定模式相關(guān)座韵。

CoreFoundation中關(guān)于RunLoop的5個類

  • CFRunLoopRef:運行循環(huán)對象,也就是它自身
  • CFRunLoopModeRef:指定runloop的運行模式踢京。作用:給事件源分組誉碴,避免互相影響,邏輯混亂瓣距。運行模式1個runLoop可以有很多個Mode黔帕,1個Mode可以有很多個Source,Observer蹈丸,Timer成黄,但是在同一時刻只能同時執(zhí)行一種Mode
  • CFRunLoopSourceRef:輸入源
  • CFRunLoopTimerRef:定時源,定時器逻杖;必須加入到runloop
  • CFRunLoopObserverRef(觀察者奋岁,觀察是否有事件)

系統(tǒng)默認注冊了 5個Mode:

  • kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個 Mode 下運行的
  • UITrackingRunLoopMode:界面跟蹤 Mode荸百,用于ScrollView 追蹤觸摸滑動闻伶,保證界面滑動時不受其他Mode 影響
  • UIInitializationRunLoopMode:在剛啟動 App 時第進入的第一個Mode,啟動完成之后就不再使用够话。
  • GSEventReceiveRunLoopMode:接收系統(tǒng)時間的內(nèi)部 Mode蓝翰,通常用不到。
  • kCFRunLoopCommonModes(比較特殊):這時一個占位用的 Mode女嘲,不是一種真正的 Mode畜份。

RunLoop觀察者介紹:

  • CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
  • Observer是監(jiān)聽RunLoop狀態(tài)的欣尼,CoreFunction向線程添加runloop observers來監(jiān)聽事件爆雹,意在監(jiān)聽事件發(fā)生時來做處理。
  • 線程除了處理輸入源媒至,RunLoop也會生成關(guān)于Run Loop行為的通知(notification)顶别。RunLoop觀察者(Run-Loop Observers)可以收到這些通知,并在線程上面使用他們來作額外的處理拒啰;如果RunLoop沒有任何源需要監(jiān)視的話驯绎,它會在你啟動之際立馬退出。

在每次運行開啟RunLoop的時候谋旦,所在線程的RunLoop會自動處理之前未處理的事件剩失,并且通知相關(guān)的觀察者屈尼。
具體的順序如下:

  1. 通知觀察者RunLoop已經(jīng)啟動
  • 通知觀察者即將要開始的定時器
  • 通知觀察者任何即將啟動的非基于端口的源
  • 啟動任何準備好的非基于端口的源
  • 如果基于端口的源準備好并處于等待狀態(tài),立即啟動拴孤;并進入步驟9
  • 通知觀察者線程進入休眠狀態(tài)
  • 將線程置于休眠知道任一下面的事件發(fā)生:
  • 某一事件到達基于端口的源
  • 定時器啟動
  • RunLoop設(shè)置的時間已經(jīng)超時
  • RunLoop被顯示喚醒
  • 通知觀察者線程將被喚醒
  • 處理未處理的事件
  • 如果用戶定義的定時器啟動脾歧,處理定時器事件并重啟RunLoop。進入步驟2
  • 如果輸入源啟動演熟,傳遞相應(yīng)的消息
  • 如果RunLoop被顯示喚醒而且時間還沒超時鞭执,重啟RunLoop。進入步驟2
  • 通知觀察者RunLoop結(jié)束芒粹。(自動釋放池

** RunLoop底層實現(xiàn)原理**
RunLoop 底層的實現(xiàn)是通過字典的形式來將 線程 和 RunLoop 來綁定的兄纺,RunLoop 可以理解為懶加載,子線程的 RunLoop 可以調(diào)用 currentRunLoop化漆,先從字典里面根據(jù)子線程取估脆,如果沒有就會去創(chuàng)建并與子線程綁定,保存到字典當中座云。每個 RunLoop 里面有很多的 Mode疙赠,每個 Mode 里面又有很多的source、timer朦拖、observer圃阳。RunLoop 在同一時刻只能執(zhí)行一種 Mode,當執(zhí)行這種 Mode 的時候贞谓,只有這種 Mode 中的source限佩、timer、observer 有效裸弦,別的 Mode 無效祟同,這樣做是為了避免邏輯的混亂。

3. 個人理解總結(jié)

runloop就是一個do-while循環(huán)理疙;
runloop就是用來接受事件源晕城,管理線程,安排線程處理事件窖贤。線程是執(zhí)行任務(wù)的砖顷。app需要持續(xù)運行,如果在主線程里赃梧,不開啟runloop滤蝠,就會關(guān)閉app。所以程序啟動的時候授嘀,在創(chuàng)建主線程的時候物咳,runloop也被系統(tǒng)創(chuàng)建了,來保持app持續(xù)運行蹄皱、安排線程處理事件览闰;
它以dic的形式跟線程綁定在一起芯肤,key是線程,value是它的runloop压鉴。創(chuàng)建方式是懶加載崖咨;
子線程開啟時,如果沒有獲取runloop油吭,執(zhí)行完任務(wù)就會銷毀击蹲,如果你想讓線程不自動銷毀,可以獲取runloop上鞠,讓runloop安排線程添加源(輸入源际邻,計時器源)并執(zhí)行任務(wù)。在添加了 source 以后,你可以給 runloop 添加 observers 來監(jiān)測 runloop 的不同的執(zhí)行的狀態(tài)芍阎。注意如果不添加源,runloop會立馬退出缨恒。

runloop有5個大類:
1. 自身對象谴咸;
2. mode:指定事件處理模式 ;在設(shè)置 RunLoopMode 以后,你的 RunLoop 就會自動過濾和其他 Mode 相關(guān)的事件源,而只監(jiān)視和當前設(shè)置 Mode 相關(guān)的源(以及通知相關(guān)的觀察者)骗露。
3. souce:事件(用戶操作岭佳,系統(tǒng)事件);
4. timer:計時器
5. Observer:給 RunLoop 注冊觀察者 Observer,以便監(jiān)控 RunLoop 的運行過程

mode補充:
主要有三個mode對象:
默認是主線程下的有以下作用:等待喚醒萧锉;安排工作優(yōu)先級順序蟋恬; 大多數(shù)工作中默認的運行方式味悄。
第二個,使用這個Mode去跟蹤來自用戶交互的事件,比如UITableView上下滑動扮超,當scroll滑動時,切換為trackmode兴革,讓scroll優(yōu)先級提高盐欺,其他事件優(yōu)先級排后,保證流暢 波附;
第三個基于上面兩個的混合體:什么時候用艺晴,即讓scroll流暢運行,也讓timer事件得到回調(diào)得以運行

Runloop退出
移除runloop的輸入源和定時器也可能導致run loop退出

使用方法:
開辟線程掸屡,獲取runloop
自定義事件源或使用系統(tǒng)端口NSPort封寞,添加到runloop;
指定mode給事件分組仅财;
添加觀察者狈究,監(jiān)聽狀態(tài);
當事件的模式與消息循環(huán)的模式匹配的時候满着,消息才會運行:讓線程即將休眠時谦炒,執(zhí)行任務(wù)贯莺;接受到用戶觸摸事件時,切換mode宁改,暫停任務(wù)缕探,保證流暢。

4. 應(yīng)用場景

1. 定時器

  • NSTimer+NSRunLoop:容易受線程堵塞影響(此文主要講解這個)
  • GCD定時器:GCD 創(chuàng)建的好處还蹲,不受 RunLoopMode 的影響爹耗。

NSTimer:
就是CFRunLoopTimerRef。
主要用于計時器的工作谜喊,當創(chuàng)建完計時器潭兽,必須要把它加入runloop中才能進行正常回調(diào)斗遏,
提問1.那么為什么要將它加入runloop山卦?
回答:這是由于計時器的功能決定的,計時器要不斷的運行休眠切換诵次,是持續(xù)性的行為账蓉。如果不放在runloop中,它無法持續(xù)進行逾一,只有runloop才能安排線程什么時候處理這個事件铸本。通過自身的observer類不斷監(jiān)聽,來進行回調(diào)休眠回調(diào)休眠遵堵。

提問2.NSTimer有什么需要注意的地方或者說有什么缺點箱玷?
回答:需要注意循環(huán)引用問題:因為NSTimer強持有target(為什么要強持有target:為了在運行中怕它被銷毀,事件的不斷持續(xù)運行時陌宿,所以要強持有)锡足,在once的情況下,一般沒有問題限番;但當repeat=YES時舱污,如果我們不主動調(diào)用invalid方法,它會在強持有target的情況下無限進行下去弥虐,造成內(nèi)存泄漏扩灯。
解決辦法:

  1. 手動調(diào)用invalid方法并置為nil
  2. 構(gòu)造一個中間類,提供傳入對象和方法的接口霜瘪,NSTimer對此對象進行強持有珠插。而此對象會自己銷毀,進而不會永遠被NSTimer所持有造成內(nèi)存泄漏颖对。

提問3. 在cell上使用NSTimer顯示倒計時捻撑,如何即保障滑動流暢又保持數(shù)據(jù)實時更新并顯示
回答:創(chuàng)建timer,手動加入到runloop中,指定mode為commonmode模式顾患。

2. ImageView顯示
另外還有一個trick是當tableview的cell從網(wǎng)絡(luò)異步加載圖片, 加載完成后在主線程刷新顯示圖片, 這時滑動tableview會造成卡頓. 通常的思路是tableview滑動的時候延遲加載圖片, 等停止滑動時再顯示圖片. 這里我們可以通過RunLoop來實現(xiàn).

[self.cellImageView performSelector:@sector(setImage:)
withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

當NSRunLoop為NSDefaultRunLoopMode的時候tableview肯定停止滑動了, why? 因為如果還在滑動中, RunLoop的mode應(yīng)該是UITrackingRunLoopMode.

3. PerformSelector:

[self performSelector:@selector(download:) withObject:url afterDelay:1.0f];
  • 當調(diào)用 NSObject 的 performSelector:afterDelay:后番捂,實際上內(nèi)部會創(chuàng)建一個 Timer 并添加到當前線程的 RunLoop 中,所以如果當前線程沒有 RunLoop江解,則這個方法會失效设预。在調(diào)用時的當前線程的runloop的default模式中運行。相當于在default中加了個定時器

4. 常駐線程
創(chuàng)建一個線程來處理耗時且頻繁的操作犁河,例如即時聊天音頻的壓縮鳖枕,或者經(jīng)常下載,避免頻繁開啟線程以便提高性能, AFNetWorking就是如此桨螺。

[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];

5. 利用Runloop預(yù)處理多個cell高度
原理:滑動的時候宾符,主線程中的runloop,會將默認的mode(處理事件)切換為trackmode灭翔,也就是說屏蔽了其他源中的事件(一種 mode對應(yīng)一種事件源)魏烫,保障滑動流暢。
預(yù)處理cell高度:分解成多個runloop source任務(wù)肝箱,不能在同一個runloop中迭代執(zhí)行则奥,因為會造成ui卡頓,這時就需要手動向 RunLoop 中添加 Source 任務(wù)狭园。可以使用performer的方法糊治,自定義事件源sourceo0任務(wù)唱矛,通過這個方法加入到指定線程的runloop中,并指定mode井辜,在給定的 Mode 下執(zhí)行绎谦,若指定的 RunLoop 處于休眠狀態(tài),則喚醒它處理事件粥脚。創(chuàng)建觀察者監(jiān)聽runloop的狀態(tài)窃肠。于是,我們用一個可變數(shù)組裝載當前所有需要“預(yù)緩存”的 index path刷允,每個 RunLoopObserver 回調(diào)時都把第一個任務(wù)拿出來分發(fā)冤留。這樣,每個任務(wù)都被分配到下個“空閑” RunLoop 迭代中執(zhí)行树灶,其間但凡有滑動事件開始纤怒,Mode 切換成 UITrackingRunLoopMode,所有的“預(yù)緩存”任務(wù)的分發(fā)和執(zhí)行都會自動暫定天通,最大程度保證滑動流暢泊窘。
runloop狀態(tài):當用戶停止滑動的時候,喚醒runloop,切換到默認mode烘豹,讓其執(zhí)行計算事件瓜贾;當用戶滑動的時候,通知runloop携悯,切換trackmode祭芦,讓其停止計算任務(wù),并休眠蚌卤。

[self performSelector:@selector(opCellheight:) onThread:sunThread withObject:url waitUntilDone:YES modes:array];

6. 滑動與圖片刷新

  • 滑動與圖片刷新:當tableView的cell上有需要從網(wǎng)絡(luò)獲取的圖片的時候实束,滾動tableView,異步線程回去加載圖片逊彭,加載完成后主線程會設(shè)置cell的圖片咸灿,但是會造成卡頓∥甓#可以設(shè)置圖片的任務(wù)在CFRunloopDefaultMode下進行避矢,當滾動tableView的時候,Runloop切換到UITrackingRunLoopMode囊榜,不去設(shè)置圖片审胸,而是而是當停止的時候,再去設(shè)置圖片卸勺。
[self performSelector:@selector(download:) withObject:url afterDelay:0 inModes:NSDefaultRunLoopMode];

場景5跟6解決方法思想差不多砂沛,但是場景5的方法創(chuàng)建了多個source任務(wù)!更高效

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曙求,一起剝皮案震驚了整個濱河市碍庵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悟狱,老刑警劉巖静浴,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異挤渐,居然都是意外死亡苹享,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門浴麻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來得问,“玉大人,你說我怎么就攤上這事白胀⊥指常” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵或杠,是天一觀的道長哪怔。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么认境? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任胚委,我火速辦了婚禮,結(jié)果婚禮上叉信,老公的妹妹穿的比我還像新娘亩冬。我一直安慰自己,他們只是感情好硼身,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布硅急。 她就那樣靜靜地躺著,像睡著了一般佳遂。 火紅的嫁衣襯著肌膚如雪营袜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天丑罪,我揣著相機與錄音荚板,去河邊找鬼。 笑死吩屹,一個胖子當著我的面吹牛跪另,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播煤搜,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼免绿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了擦盾?” 一聲冷哼從身側(cè)響起针姿,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厌衙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绞绒,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡婶希,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓬衡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喻杈。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狰晚,靈堂內(nèi)的尸體忽然破棺而出筒饰,到底是詐尸還是另有隱情,我是刑警寧澤壁晒,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布瓷们,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谬晕。R本人自食惡果不足惜碘裕,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望攒钳。 院中可真熱鬧帮孔,春花似錦、人聲如沸不撑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焕檬。三九已至姆坚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間揩页,已是汗流浹背旷偿。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留爆侣,地道東北人萍程。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像兔仰,于是被迫代替她去往敵國和親茫负。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容