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)題也可以留言一起討論。