目錄:
- App啟動原理
- RunLoop 的概念
- RunLoop 與線程的關(guān)系
- RunLoop 對外的接口
- RunLoop 的 Mode
- RunLoop 的內(nèi)部邏輯
- RunLoop 的底層實(shí)現(xiàn)
- RunLoop 實(shí)現(xiàn)的功能
- AutoreleasePool
- 事件響應(yīng)
- 手勢識別
- 界面更新
- 定時(shí)器
- PerformSelecter
- GCD與RunLoop
- 網(wǎng)絡(luò)請求與RunLoop
- RunLoop 的實(shí)際應(yīng)用舉例
- 常駐線程
- 計(jì)時(shí)器與滑動沖突
- 回光返照
- tableView卡頓優(yōu)化
啟動原理
從app啟動開始,調(diào)用堆棧的信息可以看出,程序啟動時(shí),默認(rèn)開啟了一個(gè)runLoop,存在于Thread1。

上圖中,系統(tǒng)首先調(diào)用 start函數(shù),程序開始執(zhí)行main函數(shù),
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 系統(tǒng)首先通過lib system調(diào)用start函數(shù)之后,啟動main函數(shù)(程序的唯一入口)
- 接著通過main函數(shù)的入口函數(shù)傳入程序運(yùn)行所需要的參數(shù), 所有args均保存在argv[]數(shù)組中.通過對argv[]中的參數(shù)進(jìn)行遍歷打印得出
2016-12-04 10:55:11.177 08.UI動畫特效[21542:1239589] /Users/lx/Library/Developer/CoreSimulator/Devices/E6FC0E7F-0B8B-4D76-9E98-A1A7030C5BAD/data/Containers/Bundle/Application/9AE9DF6D-0A6E-4F5B-B16E-E36954B7F9DE/08.UI???á?aáaπê?à.app/08.UI???á?aáaπê?à
第一個(gè)參數(shù)為引用程序的執(zhí)行路徑。由此得出,main函數(shù)傳入的參數(shù)為應(yīng)用程序的執(zhí)行文件,和其它的輔助參數(shù),第一個(gè)參數(shù)為應(yīng)用程序的執(zhí)行文件。 - 然后系統(tǒng)開啟了一個(gè)autoreleasepool{} 它的作用主要是為了適配ARC環(huán)境,手動的對中間創(chuàng)建的變量內(nèi)存空間進(jìn)行自動管理。由于這個(gè)釋放池包含了下面的return中的函數(shù),所以return函數(shù)中的資源,每次執(zhí)行完都會被釋放。
- 接著引用程序開啟了一個(gè)函數(shù)
UIApplicaitonMain函數(shù)。
這個(gè)函數(shù)承接main函數(shù)傳入的參數(shù)。通過上圖可以看出- 開啟了一個(gè)CFRunLoopRunSpecific,該特殊的runLoop主要是為樂保證程序在啟動的過程中不受其它外部輸入事件的干擾
- 同時(shí)也啟動了一個(gè)GSEventRunModal,此為GraphicsServices庫執(zhí)行,主要是為處理程序的硬件輸入事件。
-
__CFRunloopServiceMachPort是因?yàn)槭謩訒和?調(diào)用了系統(tǒng)內(nèi)核服務(wù),系統(tǒng)內(nèi)核給程序發(fā)送 mach_msg_trap的消息,程序進(jìn)入暫停。 - 此時(shí)如果我門不進(jìn)行暫停,則程序會進(jìn)入mainRunLoop事件循環(huán),一直接收事件。
-
This function is called in the main entry point to create the application object and the application delegate and set up the event cycle.,用蘋果官方的話來說,UIApplicaitonMain它啟動程序,同時(shí)開啟了一個(gè)UIApplicaiton的代理,通過反射的方式注冊了它的代理類AppDelegate(當(dāng)然我們業(yè)可以集成UIResponse類來自己一個(gè)代理類),同時(shí)創(chuàng)建了一個(gè)程序運(yùn)行的主runLoop,用于監(jiān)聽程序的輸入事件(如人工輸入,網(wǎng)路數(shù)據(jù)輸入).
runLoop的概念
- 下面用一段為代碼來做說明。
while(isLiving) { id wakeObj = WakeUpFromSleeping(); id Event = GetEvent(wakeObj); if(crash || userQuit){ isLiving = NO; } }
* 通過上面的為代碼將runLoop簡寫成了一個(gè)死循環(huán),在每次的循環(huán)周期類,它不停的接受事件,處理事件,等待喚醒和睡眠.
在沒有事情做的時(shí)候,它會進(jìn)入睡眠狀態(tài),釋放CPU資源,在程序接受到事件后,它會從消息隊(duì)列(messageQueue)中一條一條的將事件取出,然后進(jìn)行執(zhí)行,這里只是初步介紹一下,實(shí)際在每次runLoop過程中它還進(jìn)行了 界面刷新,GCD分派事件的調(diào)度,系統(tǒng)內(nèi)核MarchPort事件處理,以及系統(tǒng)強(qiáng)制插入的程序中的其它緊急事件處理。后面會進(jìn)行一一介紹。
* 總的來說,mainRunLoop主要干了三件事情:
+ 讓程序一直活著,保持持續(xù)的監(jiān)聽(網(wǎng)絡(luò)或者用戶的輸入)
+ 解耦,通過從消息隊(duì)列的輪詢,使得接受到的事件能有序的進(jìn)行處理(防止一個(gè)觸摸事件之后,后面的事件都沒辦法處理)。
+ 節(jié)省CPU資源,這個(gè)效果就非常明顯了,睡眠就是最好的實(shí)現(xiàn)方式。
#### RunLoop 與線程的關(guān)系
+ 下面先來看一段源碼:
```objective-c
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問 loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;
/// 獲取一個(gè) pthread 對應(yīng)的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次進(jìn)入時(shí),初始化全局Dic,并先為主線程創(chuàng)建一個(gè) RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
/// 直接從 Dictionary 里獲取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
/// 取不到時(shí),創(chuàng)建一個(gè)
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注冊一個(gè)回調(diào),當(dāng)線程銷毀時(shí),順便也銷毀其對應(yīng)的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
- 上述代碼中定義了兩個(gè)重要的函數(shù)CFRunLoopGetMain()和CFRunLoopGetCurrent();在初始化獲取的時(shí)候,首先會去全局字典中獲取,當(dāng)沒有的時(shí)候就會重新根據(jù)對應(yīng)的線程生成一個(gè)runLoop,它的key及為當(dāng)前對應(yīng)的線程,所以從這里可以看出,每個(gè)線程與它的RunLoop是一一對應(yīng)的。同時(shí)也注冊了一個(gè)線程銷毀時(shí)的回調(diào)block
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);。 - 總結(jié)他們的關(guān)系如下:
- 主線程在程序運(yùn)行的時(shí)候會創(chuàng)建唯一的一個(gè)MainRunLoop(根runLoop,實(shí)際測試還可以嵌套,因?yàn)樗鼤r(shí)while循環(huán)為前提的,在一個(gè)runLoop上可以嵌套其他的runLoop),在這里稱最上層的RunLoop為根runLoop.
- 線程與RunLoop之間是一一對應(yīng)的,子線程需要通過CFGetCurrentRunLoop才能獲取對應(yīng)的runLoop。
- 線程銷毀的時(shí)候,和它綁定的runLoop也會一起銷毀。
RunLoop關(guān)鍵類
- 通過CoreFoundation的源碼介紹中,主要包含一下幾個(gè)類.
- CFRunLoopRef
- CFRunLoopModeRef //runLoop的運(yùn)行
- CFRunLoopSourceRef //runLoop對應(yīng)的事件
- CFRunLoopTimerRef //runLoop對應(yīng)計(jì)時(shí)器
- CFRunLoopObserverRef //runLoop狀態(tài)觀察者
- 一個(gè)runLoop包含若干的Mode,常用的有NSDefaultRunLoopMode,UITrackingRunLoopMode;
- 一個(gè)Mode對應(yīng)若干個(gè)source和若干的timers,以及若干的observers,
- 蘋果規(guī)定程序一次只能運(yùn)行一個(gè)Mode,所以在runLoop切換Mode的時(shí)候只能退出當(dāng)前的Mode,再重新指定一個(gè)新的Mode,這樣做能夠?qū)⒉煌M之間的timers/sources/observes分離開來,便于管理。通常我們把runLoop運(yùn)行的模式叫做CurrentMode。
- 蘋果指定了一個(gè)非常特殊的Mode,那就是UITrackingMode,該Mode模式下,runLoop只處理滑動事件,保證用戶的滑動不受其它事件的干擾。但是這個(gè)模式會和添加到DefaultMode下的timers存在沖突,下面會講道解決方案。
- CFRunLoopSourceRef,主要是用來定義系統(tǒng)事件,和程序運(yùn)行執(zhí)行過程中的外部輸入事件,如內(nèi)核MachPort派發(fā)事件,CFSocket網(wǎng)絡(luò)資源輸入事件,外部touch事件,以及程序運(yùn)行代碼手動添加的事件。為了便于區(qū)分和管理. 蘋果設(shè)定了兩種source事件類型。
- source0: 它主要用于處理應(yīng)用程序負(fù)責(zé)管理的事件,如UIEvent事件,CFSocket輸入事件。它包含了一個(gè)回調(diào)指針,并不能主動的觸發(fā)事件,而是依靠外部的事件來觸發(fā),比如用戶點(diǎn)擊了某個(gè)地方,或者網(wǎng)絡(luò)接口的數(shù)據(jù)來了。需要先調(diào)用CFRunLoopSourceSingal(source),將這個(gè)source標(biāo)記為待處理,然后手動調(diào)用CFRunLoopWakUp(runLoop)來喚醒runLoop后處理該事件。
- source1: 主要是用來處理系統(tǒng)派發(fā)的事件,或者進(jìn)程間通信(實(shí)際用不上,iOS為單進(jìn)程,通常一次只能運(yùn)行一個(gè)app).它是由系統(tǒng)內(nèi)核來管理的,由MachPort進(jìn)行驅(qū)動,如CFMatchPort(NSPort是對它的封裝),CFMessagePort,每個(gè)進(jìn)程被操作系統(tǒng)綁定了不同的PortId,通常情況下通過對該portId發(fā)送消息會觸發(fā)source1事件。比如點(diǎn)擊Xcode的暫停,起始底層調(diào)用了mach_msg_trap,使得程序進(jìn)入暫停。
- CFRunLoopTimerRef 是基于時(shí)間的觸發(fā)器,(NSTimer是對它的上層封裝).它包括了一個(gè)事件長度和一個(gè)回調(diào)指針。當(dāng)加入到RunLoop的時(shí)候,RunLoop會注冊對應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間到的時(shí)候,RunLoop會被自己注冊的時(shí)間節(jié)點(diǎn)事件回調(diào)并喚醒,執(zhí)行注冊的時(shí)間節(jié)點(diǎn)數(shù)據(jù)。實(shí)際上我們app中有許多的時(shí)間都是依賴與NSTimer和CFRunLoopTimerRef,例如 CAAniamtion,CATransition,Push,Pop,CADisplayXXX,timerScheduleXXX,timerIntervalXXX,performDelay,PerformAfterXXX都是基于注冊時(shí)間片回調(diào)事件來執(zhí)行的。這里需要注意的是在執(zhí)行動畫操作的時(shí)候, RunLoop通常是在這一輪的循環(huán)中搜集完所有的動畫執(zhí)行事件之后再進(jìn)行集中處理。
- CFRunLoopObserverRef, 觀察者的主要作用就是監(jiān)視r(shí)unLoop的生命周期和活動變化,簡單來說吧就是 runLoop的創(chuàng)建-> 運(yùn)行-> 掛起->喚醒->.... >死亡。runLoop在每次運(yùn)行循環(huán)中把自己的狀態(tài)變化通過注冊回調(diào)指針告訴對應(yīng)的observe,這樣它的每一次狀態(tài)變化時(shí),observe都能通過回調(diào)指針獲取它對應(yīng)的狀態(tài),進(jìn)行相關(guān)的處理。附上觀察的時(shí)間點(diǎn)。
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
};
RunLoop 的 Mode
- 前面提到的 source,timer,observe 他們分別以set,array,array的方式保存在Mode中。這里先把每個(gè)source,timer,observe稱作為一個(gè)item,便于后文描述。 在RunLoop內(nèi)部做了相關(guān)的處理,當(dāng)相同的幾個(gè)Item添加到同一mode中是不會有效果的,相同的item可以添加到不同的Mode中去,當(dāng)一個(gè)Mode中沒有Item的時(shí)候,那么這個(gè)Mode就會自動退出, 這點(diǎn)不難理解,mode存在的意義就是為了讓RunLoop在指定的模式上,執(zhí)行指定的事件,當(dāng)沒有Item去執(zhí)行的時(shí)候,這個(gè)Mode也就不存在了。RunLoop也就退出了。
- CFRunLoop和CFRunLoopMode的結(jié)構(gòu)
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
};
- 這里有個(gè)
commonModes的概念, 可以將Mode標(biāo)記為Common屬性,當(dāng)切換RunLoop模式的時(shí)候,runLoop會自動將commonModelItems集合中的 items(source/timer/observe)同步到具有Common標(biāo)記的所有Mode中。它就相當(dāng)于一個(gè)占位作用,放邊我門將兩種不同的Mode下的Item內(nèi)容組合到一起執(zhí)行。 比如 我門將NSTimer添加到NSDefaultRunLoopMode中,在執(zhí)行滑動的時(shí)候,NSTimer事件就不會執(zhí)行,這是應(yīng)為在滑動的時(shí)候系統(tǒng)會以用戶的滑動為主,優(yōu)先切換到 UITrackingRunLoopMode模式下,如果我門將 timer添加到commonRunLoopMode下面,則RunLoop在切換模式的時(shí)候,會將Timer事件同步到common標(biāo)記的Mode中,繼續(xù)執(zhí)行。另外一種方式直接將Timer添加打UITrackingRunLoopMode中。
RunLoop 的內(nèi)部邏輯
-
這里引用蘋果的一張圖,這張圖也看了很多遍了,每次看都會有新的不同的見解,這里說一下我個(gè)人的看法,有什么不對的地方還請大家指教。
Snip20161204_6.png - 左邊表示了某個(gè)線程對應(yīng)RunLoop每次循環(huán)執(zhí)行的事件
- start表示runLoop開始執(zhí)行,end表示本次循環(huán)結(jié)束
- runUntilDate: 表示runTime執(zhí)行在循環(huán)監(jiān)聽,直到監(jiān)聽到需要處理某件事情。這一點(diǎn)可以通過它下方定義的四個(gè)事件看出。
- handlePort: 應(yīng)該是處理端口發(fā)送的消息事件,主要是來自系統(tǒng)內(nèi)核派發(fā)的事件。
- customSrc: 用戶產(chǎn)生的source事件,例如,UIEvent事件,活著其它代碼觸發(fā)的事件。
- mySelector: 用戶定義了@selector事件。
- timerFired:主要用于界面刷新,及其它和NSTimer相關(guān)的需要注冊事件回調(diào)的事件。
- 右邊定義了兩大塊的輸入事件,inputSources:
- 其中Port 是基于系統(tǒng)內(nèi)核的 inputSource事件, 主要來自于MachPort的消息事件。 custom為用戶輸入產(chǎn)生的source事件,如UIEvent。performSelector:OnThread:強(qiáng)行往某個(gè)線程中注冊方法,主要用于線程間通信。對應(yīng)線程會在合適的時(shí)間執(zhí)行這個(gè)方法
- timer sources ,這一類時(shí)間主要基于注冊時(shí)間節(jié)點(diǎn)的回調(diào)指針來實(shí)現(xiàn),比如 界面的周期性刷新,畫面專場動畫,延遲執(zhí)行,計(jì)時(shí)器任務(wù)等。
- 接下來看看RunLoop每次while循環(huán)中執(zhí)行了哪些事
/// 此處為程序啟動時(shí)指定的RunLoop用DefaultMode啟動
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動,允許設(shè)置RunLoop超時(shí)時(shí)間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的實(shí)現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根據(jù)modeName找到對應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒有source/timer/observer, 直接返回
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù),進(jìn)入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
/// ? 一個(gè)基于 port 的Source 的事件。
/// ? 一個(gè) Timer 到時(shí)間了
/// ? RunLoop 自身的超時(shí)時(shí)間到了
/// ? 被其他什么調(diào)用者手動喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,處理消息。
handle_msg:
/// 9.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進(jìn)入loop時(shí)參數(shù)說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調(diào)用者強(qiáng)制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個(gè)都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時(shí),mode里沒空,loop也沒被停止,那繼續(xù)loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
- 上述代碼描述了runLoop運(yùn)行過程中的一次完整的執(zhí)行流程.
- 首先告知 observe將要進(jìn)入RunLoop此次事件循
- 通知observe將要處理timer(基于timer時(shí)間片注冊的回調(diào)時(shí)間)
- 通知observe將要處理source0事件(外界觸發(fā)產(chǎn)生的事件,同常是從消息隊(duì)列依次取出執(zhí)行)。
- 處理source0事件(從messageQueue取出事件執(zhí)行)
- 看是否有基于MachPort派發(fā)的事件,有則直接進(jìn)行條轉(zhuǎn)到上圖步驟9中下方,
- 判斷timer注冊事件是否到了并執(zhí)行
- 判斷有無GCD派發(fā)到主線程的事件并執(zhí)行
- 如果是基于MachPort的事件觸發(fā)了,則執(zhí)行machPort事件。
- 如果沒有事件執(zhí)行,則進(jìn)行掛起,等待外界通知,timer喚醒,machPort發(fā)送消息喚醒,活著被其它調(diào)用這手動喚醒
- 最后在程序退出的時(shí)候結(jié)束每次循環(huán)。
- 總的來說在一次循環(huán)過程中,它需要干著么多事情, 告訴觀察者自己的狀態(tài),以便觀察者在合適的時(shí)機(jī)(通常用于休眠時(shí)處理對應(yīng)注冊的事件) ,注冊timer回調(diào)事件,處理消息隊(duì)列添加的事件,處理手動加入的block塊事件,處理GCD派發(fā)的事件,處理來自系統(tǒng)內(nèi)核發(fā)送的指令事件,實(shí)時(shí)的判定是否超時(shí),是否接到外部退出指令事件。
RunLoop 功能實(shí)現(xiàn)
CFRunLoop {
current mode = kCFRunLoopDefaultMode
common modes = {
UITrackingRunLoopMode
kCFRunLoopDefaultMode
}
common mode items = {
// source0 (manual)
CFRunLoopSource {order =-1, {
callout = _UIApplicationHandleEventQueue}}
CFRunLoopSource {order =-1, {
callout = PurpleEventSignalCallback }}
CFRunLoopSource {order = 0, {
callout = FBSSerialQueueRunLoopSourceHandler}}
// source1 (mach port)
CFRunLoopSource {order = 0, {port = 17923}}
CFRunLoopSource {order = 0, {port = 12039}}
CFRunLoopSource {order = 0, {port = 16647}}
CFRunLoopSource {order =-1, {
callout = PurpleEventCallback}}
CFRunLoopSource {order = 0, {port = 2407,
callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
CFRunLoopSource {order = 0, {port = 1c03,
callout = __IOHIDEventSystemClientAvailabilityCallback}}
CFRunLoopSource {order = 0, {port = 1b03,
callout = __IOHIDEventSystemClientQueueCallback}}
CFRunLoopSource {order = 1, {port = 1903,
callout = __IOMIGMachPortPortCallback}}
// Ovserver
CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
callout = _wrapRunLoopWithAutoreleasePoolHandler}
CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting
callout = _UIGestureRecognizerUpdateObserver}
CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit
callout = _afterCACommitHandler}
CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
callout = _wrapRunLoopWithAutoreleasePoolHandler}
// Timer
CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,
next fire date = 453098071 (-4421.76019 @ 96223387169499),
callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}
},
modes = {
CFRunLoopMode {
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode {
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode {
sources0 = {
CFRunLoopSource {order = 0, {
callout = FBSSerialQueueRunLoopSourceHandler}}
},
sources1 = (null),
observers = {
CFRunLoopObserver >{activities = 0xa0, order = 2000000,
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
)},
timers = (null),
},
CFRunLoopMode {
sources0 = {
CFRunLoopSource {order = -1, {
callout = PurpleEventSignalCallback}}
},
sources1 = {
CFRunLoopSource {order = -1, {
callout = PurpleEventCallback}}
},
observers = (null),
timers = (null),
},
CFRunLoopMode {
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
}
}
}
- 從上面的代碼可以看出, runLoop默認(rèn)為DefaultRunLoop模式,蘋果對外暴露了兩個(gè)Mode, kCFRunLoopDefaultMode,程序在空閑時(shí)運(yùn)行的默認(rèn)Mode,UITrackingMode,程序在滑動的時(shí)候的Mode。他們兩個(gè)都被標(biāo)示上了Common標(biāo)記。程序在正常運(yùn)行的時(shí)候一般會在這兩個(gè)模式中間進(jìn)行自由切換,如果采用Timer計(jì)時(shí)器需要將其也添加到CommonMode中,這樣在下一次計(jì)時(shí)器回調(diào)操作里面才會被執(zhí)行。
- 在commonMode Items中,包含了runLoop運(yùn)行所需的sources,timers,observes;
- source0 默認(rèn)包含了三個(gè)回調(diào)指針, 基于外部條件輸入事件觸發(fā), _UIApplicationHandleEventQueue主要用于處理UIEvent事件,F(xiàn)BSSerialQueueRunLoopSourceHandler主要用于處理應(yīng)用程序內(nèi)部服務(wù)事件。PurpleEventSignalCallback特定的回調(diào)事件。
- source1事件主要基于matchPort觸發(fā),定義了四種回調(diào)分別處理系統(tǒng)級別的通知事件。
- Observer,_wrapRunLoopWithAutoreleasePoolHandler主要用于管理對應(yīng)的自動釋放池的創(chuàng)建和銷毀,主要是為了保證在每次掛起和運(yùn)行期間所有的使用的變量都能正常的銷毀和釋放。將要開始運(yùn)行時(shí),創(chuàng)建自動釋放池,將掛起活著退出時(shí),銷毀自動釋放池。 _UIGestureRecognizerUpdateObserver 手勢識別的監(jiān)聽者,保證手勢滑動與runLoop循環(huán)不沖突。_afterCACommitHandler和_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv 應(yīng)該是為了告知?jiǎng)赢媹?zhí)行對象當(dāng)前runLoop的執(zhí)行狀態(tài)。
- timer: 主要是為定義app調(diào)度的時(shí)間片,以及對應(yīng)時(shí)間節(jié)點(diǎn)需要喚醒執(zhí)行的的回調(diào)方法。
- 在系統(tǒng)內(nèi)部還存在很多子定義的Mode,如UIInitializationRunLoopMode,主要是用于程序啟動過程中,確保程序運(yùn)行所需調(diào)度的資源不被中斷,程序能正常啟動。GSEventReceiveRunLoopMode注冊程序內(nèi)部的接受事件的Mode。
- runLoop的事件執(zhí)行均是通一系列的回調(diào)指針執(zhí)行的,如下代碼所示:
{
/// 1. 通知Observers,即將進(jìn)入RunLoop
/// 此處有Observer會創(chuàng)建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
/// 2. 通知 Observers: 即將觸發(fā) Timer 回調(diào)。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即將觸發(fā) Source (非基于port的,Source0) 回調(diào)。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 觸發(fā) Source0 (非基于port的) 回調(diào)。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(s ource0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即將進(jìn)入休眠
/// 此處有Observer釋放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. 通知Observers,線程被喚醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. 如果是被Timer喚醒的,回調(diào)Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch喚醒的,執(zhí)行所有調(diào)用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件喚醒了,處理這個(gè)事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
/// 10. 通知Observers,即將退出RunLoop
/// 此處有Observer釋放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION_ _(kCFRunLoopExit);
}
AutoreleasePool
- 在app啟動的時(shí)候,manRunLoop的observe中就多了兩個(gè)_wrapRunLoopWithAutoreleasePoolHandler(),他們的優(yōu)先級級別在RunLoopEntry階段最高, BeforeWaiting階段最低,目的就是為了保證在在執(zhí)行其它回調(diào)函數(shù)之前優(yōu)先執(zhí)行 _objc_autoreleasePoolPush()創(chuàng)建自動釋放池,進(jìn)行內(nèi)存管理,在runLoop將要掛起的時(shí)候等待其它回調(diào)函數(shù)都執(zhí)行完了最后才執(zhí)行_objc_autoreleasePoolPush()將自動釋放池釋放,保證分配的臨時(shí)變量都被銷毀。這樣所有事件的回調(diào)都是自動釋放池的創(chuàng)建和銷毀之間執(zhí)行,從而有效避免了內(nèi)存泄漏。
事件響應(yīng)
- 從上面代碼可以看出,source1事件有一個(gè)__IOHIDEventSystemClientQueueCallback指針,它主要用于接收來自系統(tǒng)級別的事件,當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃)發(fā)生后,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收。這個(gè)過程的詳細(xì)情況可以參考這里。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進(jìn)程。隨后app通過內(nèi)部source0注冊_UIApplicationHandleEventQueue事件,將產(chǎn)生的事件在應(yīng)用程序內(nèi)進(jìn)行派發(fā)。通過UIWindow->rootViewController-> subViewControllers/subViews->target(執(zhí)行則處理事件)-(不執(zhí)行則傳遞給上一層)> superViewControllers/superView->UIWindow->UIApplication.
手勢識別
- 當(dāng)_UIApplicationHandleEventQueue()時(shí),如果檢測到了手勢,則touchesXXX方法會被打斷,隨后系統(tǒng)通將對應(yīng)的UIGestureRecognizer標(biāo)記為待處理。然后通過RunLoop注冊的觀察者,當(dāng)_UIGestureRecognizerUpdateObserver()對runLoop的狀態(tài)進(jìn)行監(jiān)聽,如果發(fā)現(xiàn)runLoop將要睡覺,系統(tǒng)就會獲取之前標(biāo)記的手勢,讓runLoop去處理手勢的回調(diào)事件。
界面更新
當(dāng)在操作UI,比如改變了frame,更新了UIView/CALayer的層次時(shí),或者手動調(diào)用了setNeedsLayout和setNeedsDisplay方法后將這個(gè)UIView或者CALayer標(biāo)記為待處理。并提交到一個(gè)全局的容器中去. 由于前面蘋果對RunLoop也注冊了一個(gè)_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv觀察者,當(dāng)觀察者發(fā)現(xiàn)runLoop將要休息或者空閑的時(shí)候,就會讓runloop去取前面的標(biāo)記事件去處理。 這也是為甚么如果界面其它source0事件處理耗時(shí)較長的時(shí)候主界面會的原因,因?yàn)橐话阌脩糇远x的事件是在runLoop正常運(yùn)行時(shí)就開始執(zhí)行,而手勢和界面動畫,布局相關(guān)的事件是在 runLoop收集完這一次循環(huán)中標(biāo)記的所有事件后,并且將要休眠的時(shí)候才處理,排布的優(yōu)先級別較高,如果前面的事件耗時(shí)較久,超時(shí)了,就會導(dǎo)致后面的界面刷新卡頓,或者直接放次本次循環(huán)的刷新。
定時(shí)器
NSTimer基于CFRunLoopTimerRef封裝,當(dāng)一個(gè)NSTimer注冊到RunLoop中后,runLoop會為其重復(fù)的事件點(diǎn)注冊好事件,為了節(jié)省資源,通常情況下runLoop并不會在非常精確的事件點(diǎn)執(zhí)行這個(gè)事件,通常會在注冊事件的時(shí)候指定一個(gè)tolerance(誤差值),當(dāng)標(biāo)示的時(shí)間節(jié)點(diǎn)到后,會直接執(zhí)行,如果執(zhí)行的事件較長,超過了下一次的事件節(jié)點(diǎn),那么系統(tǒng)就會放次該次的執(zhí)行。CADisplayLink定義了一個(gè)和屏幕刷新頻率相同的計(jì)時(shí)器。
PerformSelecter
當(dāng)我門調(diào)用PerformXXX方法時(shí),系統(tǒng)內(nèi)部會自動創(chuàng)建一個(gè)timer去執(zhí)行該事件。如果當(dāng)前的線程沒有runLoop,那么該事件便會失效
GCD與RunLoop
- 從上面runLoop每次while執(zhí)行的順序可以看出,有一段是用來詢問GCD是否有派遣到mainrunLoop中的任務(wù)。
- 當(dāng)調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時(shí),libDispatch 會向主線程的 RunLoop 發(fā)送消息,RunLoop會被喚醒,并從消息中取得這個(gè) block,并在回調(diào) CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里執(zhí)行這個(gè) block。但這個(gè)邏輯僅限于 dispatch 到主線程,dispatch 到其他線程仍然是由 libDispatch 處理的。
網(wǎng)絡(luò)請求與RunLoop
- CFSocket是底層的網(wǎng)絡(luò)接口,主要負(fù)責(zé)socket之間的通信。通過遠(yuǎn)程主機(jī)與目標(biāo)主機(jī)之間的IP+PortId傳輸數(shù)據(jù)到指定的緩沖區(qū)。 CFNetwork基于CFScoket的封裝
- NSURLConnection基于CFNetwork封裝,提供面向?qū)ο蟮慕涌?目前已經(jīng)被拋棄。
- NSURLSession用于取代NSURLConnection,底層實(shí)現(xiàn)和NSURLConnection相似。通過底層常駐的資源加載子線程(com.apple.NSURLConnectionLoader )接收來自socket的Source1事件。
- 在網(wǎng)絡(luò)傳輸時(shí),NSURLConnection默認(rèn)創(chuàng)建了兩個(gè)線程,com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private. 其中CFSocket線程主要處理底層進(jìn)程間端口通信的。在NSURLConnectionLoader這個(gè)線程會使用RunLoop來接收來自底層socket的source1事件。并通過之前添加的 Source0 通知到上層的 Delegate。下圖中 CFMultiplexerSource 是負(fù)責(zé)各種 Delegate 回調(diào)的,CFHTTPCookieStorage 是處理各種 Cookie 的。
如下圖所示:
![Upload Snip20161204_7.png failed. Please try again.]
常駐線程
- 下面這段代碼為AFNetworking中的常駐線程代碼實(shí)現(xiàn)過程,定義了一個(gè)一次任務(wù)的子線程,在創(chuàng)建線程的通知指定了一個(gè)runLoop,并通過給runLoop添加監(jiān)聽的端口,保持線程不被銷毀。這樣只需要調(diào)用performSelector:onThreadXXX就能讓該線程執(zhí)行我們的任務(wù)。
+(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; } +(void)networkRequestThreadEntryPoint:(id)__unused object{ @autoreleasepool{ //設(shè)置線程名,獲取currentRunloop。設(shè)置currentRunLoopPort用語循環(huán)監(jiān)聽,保證runLoop不被退出。 [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop* runLooop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMatchPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } }
計(jì)時(shí)器與滑動沖突
- 通過利用runLoop的相關(guān)特性,可以將NSTimer事件添加到CommonModes中來避免與UITrackingMode的沖突。
回光返照
- 在runLoop退出時(shí),直接獲取當(dāng)前的runLoop,并獲取到它所有的items,讓程序再跑一會,比如做一個(gè)友情提示.
CFRunLoopRef runloop = CFRunLoopGetCurrent();
NSArray* allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while(1){
for(NSString* mode in allModes ) {
CFRunLoopRunInMode((CFStringRef)mode,0.001,false);
}
}
tableView卡頓優(yōu)化
- 比如我們通常會在子線程加載圖片并在主線程設(shè)置圖片,但如果用戶滑動速度夠快,需同時(shí)設(shè)置的圖片數(shù)量叫多,這時(shí)候?yàn)榱吮苊饪D,則可以通過設(shè)置圖片延遲執(zhí)行,活著添加到NSDefaultRunLoopMode中來避免。
[self.headImage performSelector:@selector(setImage:) withObject:downLoadImage delay:0 inMode:NSDefaultRunLoopMode];
參考文獻(xiàn):
coreFoundation源碼: http://opensource.apple.com/tarballs/CF/CF-855.17.tar.gz
runLoop線下分享: http://www.ikoo8.com/video_play/XODgxODkzODI0.html
