RunLoop筆記

RunLoop是iOS和OSX中基本的概念土砂,掌握RunLoop州既,能了解到蘋(píng)果是如何利用RunLoop實(shí)現(xiàn)自動(dòng)釋放池、延遲回調(diào)萝映、觸摸事件吴叶、屏幕刷新等功能的,并能在開(kāi)發(fā)中優(yōu)化你的程序序臂。

蘋(píng)果提供了兩個(gè)對(duì)象:NSRunLoop 和 CFRunLoopRef蚌卤。
NSRunLoop是對(duì)CFRunLoopRef的封裝,提供了面向?qū)ο蟮腁PI,這些API并不是線程安全逊彭。
CFRunLoopRef提供C函數(shù)API咸灿,并且API都是線程安全的。
CFRunLoopRef源碼可以在這里下載閱讀诫龙。

RunLoop的概念

RunLoop可以理解為會(huì)在內(nèi)部卡住的do-while循環(huán)析显,當(dāng)某個(gè)事件繼續(xù)驅(qū)動(dòng)循環(huán)時(shí),循環(huán)得以往下執(zhí)行或者退出签赃。例如下面的偽代碼:
objectivec
do{
dothing();
sleep();
//代碼停在這一行谷异,等待喚醒消息。
id message = wakeUpMessage();
doMessage(message);
}while(message != quit || != loopStop);

RunLoop的核心就是這個(gè)事件驅(qū)動(dòng)循環(huán)(Event Loop)锦聊。在線程中執(zhí)行這個(gè)循環(huán)后歹嘹,在一直處于循環(huán)內(nèi)部"等待->接受消息->處理消息->等待"狀態(tài),直到傳入了退出循環(huán)的消息或者循環(huán)退出孔庭。

##RunLoop的結(jié)構(gòu)
有五個(gè)類與RunLoop相關(guān):
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef


![RunLoop與mode的關(guān)系.png](http://upload-images.jianshu.io/upload_images/1353363-dc3b33413e96fe75.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


一個(gè)RunLoop中可以有多個(gè)Mode尺上,一個(gè)Mode可以有多個(gè)Source/Timer/Observer。

###CFRunLoopSourceRef
soure包含兩個(gè)版本:
- soure0:處理App內(nèi)部事件圆到,App自己負(fù)責(zé)管理這些時(shí)間怎抛,比如UIEvent和CFSocket。
- soure1:由RunLoop和內(nèi)核管理芽淡,由Mach port驅(qū)動(dòng)马绝,如CFMackPort、CFMessagePort挣菲,用于線程間通信富稻。

###CFRunLoopTimerRef
我們開(kāi)發(fā)中經(jīng)常使用的NSTimer就是來(lái)自于這里。當(dāng)其加入到RunLoop時(shí)白胀,RunLoop會(huì)在相應(yīng)的時(shí)間點(diǎn)注冊(cè)好回調(diào)椭赋,當(dāng)時(shí)間點(diǎn)到達(dá)時(shí),RunLoop被喚醒以執(zhí)行回調(diào)或杠。

###CFRunLoopObserverRef
observer是觀察者哪怔。每個(gè)Observer都包含一個(gè)回調(diào),用于當(dāng)RunLoop發(fā)生狀態(tài)變化時(shí)向抢,觀察者能接收到相應(yīng)的回調(diào)认境。可以接收到的回調(diào)有:
```objectivec```
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
};

CFRunLoopModeRef

結(jié)構(gòu)如下:
objectivec
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
};

每個(gè)Mode除了上面提到的sources/observer/timer外笋额,還對(duì)應(yīng)著一個(gè)ModeName。蘋(píng)果公開(kāi)提供的 Mode 有兩個(gè):NSDefaultRunLoopMode和 UITrackingRunLoopMode篷扩,你可以用這兩個(gè) Mode Name 來(lái)操作其對(duì)應(yīng)的 Mode兄猩。

需要注意的是RunLoop在同一時(shí)間內(nèi)只能且必須在一個(gè)Mode下運(yùn)行,否則RunLoop無(wú)法運(yùn)行。并且當(dāng)需要更換Mode時(shí)枢冤,需要停止RunLoop鸠姨,重新賦值Mode,然后重新啟動(dòng)RunLoop淹真。

###RunLoop 的內(nèi)部邏輯

![RunLoop內(nèi)部邏輯.png](http://upload-images.jianshu.io/upload_images/1353363-5c2dfbba6995f5d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


通過(guò)整理后的偽代碼如下:
```objectivec```
id runLoop{
    //通過(guò)Observer通知讶迁,即將進(jìn)入Loop
    runLoopObserver(kCFRunLoopEntry);
    //進(jìn)入RunLoop內(nèi)部循環(huán)
    runLoopRun(){
      do{
         //通過(guò)Observer通知,RunLoop 即將觸發(fā) Timer 回調(diào)核蘸。
         runLoopObserver(kCFRunLoopBeforeTimers);
         //通過(guò)Observer通知巍糯,RunLoop 即將觸發(fā) Source0回調(diào)。
        runLoopObserver(kCFRunLoopBeforeSources);

         doTimer();
         doSource0();

         //如果有source1消息客扎,跳過(guò)睡眠
         if(source1) goto handle_msg;

         //通過(guò)Observer通知祟峦,RunLoop 的線程即將進(jìn)入休眠(sleep)。
         runLoopObserver(kCFRunLoopBeforeWaiting);
         //RunLoop等待被喚醒
         //Z z z...
         //通過(guò)Observer通知徙鱼,RunLoop被喚醒宅楞。
         runLoopObserver(kCFRunLoopAfterWaiting);
       
handle_msg:
         //根據(jù)被喚醒的類型,處理消息
        }while(!stop);
    //通過(guò)Observer通知袱吆,RunLoop 即將退出厌衙。
    runLoopObserver(kCFRunLoopExit);
    }
}

RunLoop的應(yīng)用

滑動(dòng)優(yōu)化

主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。DefaultMode 是 App 平時(shí)所處的狀態(tài)绞绒,TrackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)婶希。將Timer或者其它消耗性能的回調(diào)注冊(cè)在kCFRunLoopDefaultMode,當(dāng)頁(yè)面進(jìn)行滑動(dòng)時(shí)处铛,都會(huì)停止這些回調(diào)饲趋。不會(huì)影響到滑動(dòng)操作。

AutoreleasePool

當(dāng)即將進(jìn)入Loop撤蟆,會(huì)創(chuàng)建AutoreleasePool奕塑,其優(yōu)先級(jí)最高,保證在發(fā)生其它回調(diào)之前家肯。當(dāng)RunLoop即將進(jìn)入休眠時(shí)龄砰,會(huì)pop掉舊的AutoreleasePool,創(chuàng)建新的AutoreleasePool讨衣。最后RunLoop退出時(shí)换棚,釋放AutoreleasePool,其優(yōu)先級(jí)最低反镇,保證其釋放池發(fā)生在其他所有回調(diào)之后固蚤。其中的Autorelease對(duì)象在AutoreleasePool釋放后被釋放。

AFN中RunLoop的應(yīng)用

AFNetworking 單獨(dú)創(chuàng)建了一個(gè)線程歹茶,并在這個(gè)線程中啟動(dòng)了一個(gè) RunLoop:
objectivec

  • (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
    [[NSThread currentThread] setName:@"AFNetworking"];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
    }
    }

  • (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
    _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
    [_networkRequestThread start];
    });
    return _networkRequestThread;
    }

其中添加的[NSMachPort port]其實(shí)是一個(gè)空port夕玩,目的只是讓RunLoop不至于退出你弦,達(dá)到線程保活燎孟。


###AsyncDisplayKit
我們知道禽作,當(dāng)UI 線程中一旦出現(xiàn)繁重的任務(wù)就會(huì)導(dǎo)致界面卡頓。而AsyncDisplayKit的做法就是將這些任務(wù)在后臺(tái)執(zhí)行揩页,并監(jiān)聽(tīng)主線程的Observer旷偿。當(dāng)主線程RunLoop進(jìn)入 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit狀態(tài)時(shí),遍歷所有之前放入隊(duì)列的待處理的任務(wù)爆侣,然后一一執(zhí)行萍程。

###最后
此文章概括得比較簡(jiǎn)單,大多數(shù)內(nèi)容都是參考:
- [深入理解RunLoop](http://blog.ibireme.com/2015/05/18/runloop/)
- [iOS線下分享《RunLoop》by 孫源@sunnyxx](http://v.youku.com/v_show/id_XODgxODkzODI0.html)

再加上自己的一點(diǎn)筆記累提。有錯(cuò)誤的地方希望大家可以留言改正尘喝,有什么問(wèn)題也可以留言一起討論。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斋陪,一起剝皮案震驚了整個(gè)濱河市朽褪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌无虚,老刑警劉巖缔赠,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異友题,居然都是意外死亡嗤堰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)度宦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)踢匣,“玉大人,你說(shuō)我怎么就攤上這事戈抄±牖#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵划鸽,是天一觀的道長(zhǎng)输莺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)裸诽,這世上最難降的妖魔是什么嫂用? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮丈冬,結(jié)果婚禮上嘱函,老公的妹妹穿的比我還像新娘。我一直安慰自己埂蕊,他們只是感情好往弓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布橄浓。 她就那樣靜靜地躺著,像睡著了一般亮航。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匀们,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天缴淋,我揣著相機(jī)與錄音,去河邊找鬼泄朴。 笑死重抖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祖灰。 我是一名探鬼主播钟沛,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼局扶!你這毒婦竟也來(lái)了恨统?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤三妈,失蹤者是張志新(化名)和其女友劉穎畜埋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體畴蒲,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悠鞍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了模燥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咖祭。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蔫骂,靈堂內(nèi)的尸體忽然破棺而出么翰,到底是詐尸還是另有隱情,我是刑警寧澤纠吴,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布硬鞍,位于F島的核電站,受9級(jí)特大地震影響戴已,放射性物質(zhì)發(fā)生泄漏固该。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一糖儡、第九天 我趴在偏房一處隱蔽的房頂上張望伐坏。 院中可真熱鬧,春花似錦握联、人聲如沸桦沉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纯露。三九已至剿骨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間埠褪,已是汗流浹背浓利。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钞速,地道東北人贷掖。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像渴语,于是被迫代替她去往敵國(guó)和親苹威。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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