簡(jiǎn)單的說(shuō)run loop是事件驅(qū)動(dòng)的一個(gè)大循環(huán),如下代碼所示:
int main(int argc, char * argv[]) {
//程序一直運(yùn)行狀態(tài)
while (AppIsRunning) {
//睡眠狀態(tài),等待喚醒事件
id whoWakesMe = SleepForWakingUp();
//得到喚醒事件
id event = GetEvent(whoWakesMe);
//開始處理事件
HandleEvent(event);
}
return 0;
}
-----------------------------runloop 與Cocoa息息相關(guān)---------------------------------------
1、相關(guān)知識(shí):
自動(dòng)釋放池、延遲回調(diào)、觸摸事件、屏幕刷新、定時(shí)器(NSTimer)、GCD,mach kernel,block,pthread,NSObject(NSThreadPerformAddition),CADisplayLink,CATransition,CAAnimation,dispatch_get_main_queue(),NSPort,NSURLConnection,AFNetworking,這么說(shuō)來(lái)跟很多知識(shí)都有關(guān)聯(lián)。
2、CFRunLoopRef構(gòu)造:數(shù)據(jù)結(jié)構(gòu);創(chuàng)建與退出;mode切換和item依賴;Runloop啟動(dòng)
3、Runloop內(nèi)部邏輯:關(guān)鍵在兩個(gè)判斷點(diǎn)(是否睡覺,是否退出)
4、Runloop本質(zhì):mach port和mach_msg()
5、如何處理界面刷新
6、如何處理UI事件響應(yīng)
7、如何處理手勢(shì)
8、如何處理GCD任務(wù)
9、如何處理timer(與CADisplayLink)
10、如何處理performSelector
11、如何處理網(wǎng)絡(luò)請(qǐng)求
12、Runloop 常見應(yīng)用
1、與Runloop相關(guān)的模塊
先簡(jiǎn)單介紹runloop的功能,以及涉及到的模塊
【線程】 Runloop的寄生于線程:一個(gè)線程只能有唯一對(duì)應(yīng)的runloop;但這個(gè)根runloop里可以嵌套子runloops;線程剛創(chuàng)建時(shí)并沒有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。在任何一個(gè)Cocoa程序的線程中,都可以通過(guò):NSRunLoop *runloop = [NSRunLoop currentRunLoop];
線程(創(chuàng)建)-->runloop將進(jìn)入-->最高優(yōu)先級(jí)OB創(chuàng)建釋放池-->runloop將睡-->最低優(yōu)先級(jí)OB銷毀舊池創(chuàng)建新池-->runloop將退出-->最低優(yōu)先級(jí)OB銷毀新池-->線程(銷毀)
主線程 (有 RunLoop 的線程) 幾乎所有函數(shù)都從以下六個(gè)之一的函數(shù)調(diào)起(可以通過(guò)xcode斷點(diǎn)調(diào)試,看函數(shù)棧):
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRunloop is calling out to an abserver callback function
用于向外部報(bào)告 RunLoop 當(dāng)前狀態(tài)的更改,框架中很多機(jī)制都由 RunLoopObserver 觸發(fā),如 CAAnimation
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRunloop is calling out to a block
消息通知、非延遲的perform、dispatch調(diào)用、block回調(diào)、KVO
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
CFRunloop is servicing the main desipatch queue
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRunloop is calling out to a timer callback function
延遲的perform, 延遲dispatch調(diào)用
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
CFRunloop is calling out to a source 0 perform function
處理App內(nèi)部事件、App自己負(fù)責(zé)管理(觸發(fā)),如UIEvent、CFSocket。普通函數(shù)調(diào)用,系統(tǒng)調(diào)用
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRunloop is calling out to a source 1 perform function
由RunLoop和內(nèi)核管理,Mach port驅(qū)動(dòng),如CFMachPort、CFMessagePort
【自動(dòng)釋放池】自動(dòng)釋放池寄生于Runloop:程序啟動(dòng)后,蘋果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler(),一是,監(jiān)測(cè)Entry(即將進(jìn)入Loop)狀態(tài),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池,其 order 是-2147483647,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前;另外一個(gè),Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來(lái)釋放自動(dòng)釋放池。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
下面將介紹runloop的結(jié)構(gòu),然后繼續(xù)介紹【觸摸事件】,【屏幕刷新】,【定時(shí)器】,【屏幕刷新】等。
2. CFRunLoopRef構(gòu)造:
NSRunloop是對(duì)CFRunloopRef的面向?qū)ο蠓庋b,但是不是線程安全,CFRunloopRef數(shù)據(jù)結(jié)構(gòu)如下:
// runloop數(shù)據(jù)結(jié)構(gòu)
struct __CFRunLoopMode {
CFStringRef _name; // Mode名字,
CFMutableSetRef _sources0; // Set<CFRunLoopSourceRef>
CFMutableSetRef _sources1; // Set<CFRunLoopSourceRef>
CFMutableArrayRef _observers; // Array<CFRunLoopObserverRef>
CFMutableArrayRef _timers; // Array<CFRunLoopTimerRef>
...
};
// mode數(shù)據(jù)結(jié)構(gòu)
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set<CFStringRef>
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set<CFRunLoopModeRef>
...
};
創(chuàng)建與退出:mode切換和item依賴
a 主線程的runloop自動(dòng)創(chuàng)建,子線程的runloop默認(rèn)不創(chuàng)建(在子線程中調(diào)用NSRunLoop *runloop = [NSRunLoop currentRunLoop];獲取RunLoop對(duì)象的時(shí)候,就會(huì)創(chuàng)建RunLoop);
b runloop退出的條件:app退出;線程關(guān)閉;設(shè)置最大時(shí)間到期;modeItem為空;
c 同一時(shí)間一個(gè)runloop只能在一個(gè)mode,切換mode只能退出runloop,再重進(jìn)指定mode(隔離modeItems使之互不干擾);
d 一個(gè)item可以加到不同mode;一個(gè)mode被標(biāo)記到commonModes里(這樣runloop不用切換mode)。
啟動(dòng)Runloop:
// 用DefaultMode啟動(dòng)
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
// 用指定的Mode啟動(dòng),允許設(shè)置RunLoop最大時(shí)間(假無(wú)限循環(huán)),執(zhí)行完畢是否退出
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
CFRunLoopModeRef:
數(shù)據(jù)結(jié)構(gòu)(見上);
創(chuàng)建添加:runloop自動(dòng)創(chuàng)建對(duì)應(yīng)的mode;mode只能添加不能刪除
// 添加mode
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
類型:
- kCFRunLoopDefaultMode: 默認(rèn) mode,通常主線程在這個(gè) Mode 下運(yùn)行。
- UITrackingRunLoopMode: 追蹤mode,保證Scrollview滑動(dòng)順暢不受其他 mode 影響。
- UIInitializationRunLoopMode: 啟動(dòng)程序后的過(guò)渡mode,啟動(dòng)完成后就不再使用。
4: GSEventReceiveRunLoopMode: Graphic相關(guān)事件的mode,通常用不到。
5: kCFRunLoopCommonModes: 占位mode,作為標(biāo)記DefaultMode和CommonMode用。
modeItems
// 添加移除item的函數(shù)(參數(shù):添加/移除哪個(gè)item到哪個(gè)runloop的哪個(gè)mode下)
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopSourceRef:事件來(lái)源
按照官方文檔CFRunLoopSourceRef為3類,但數(shù)據(jù)結(jié)構(gòu)只有兩類(???)
Port-Based Sources:與內(nèi)核端口相關(guān)
Custom Input Sources:與自定義source相關(guān)
Cocoa Perform Selector Sources:與PerformSEL方法相關(guān))
數(shù)據(jù)結(jié)構(gòu)(source0/source1);
// source0 (manual): order(優(yōu)先級(jí)),callout(回調(diào)函數(shù))
CFRunLoopSource {order =..., {callout =... }}
// source1 (mach port):order(優(yōu)先級(jí)),port:(端口), callout(回調(diào)函數(shù))
CFRunLoopSource {order = ..., {port = ..., callout =...}
source0:event事件,只含有回調(diào),需要標(biāo)記待處理(signal),然后手動(dòng)將runloop喚醒(wakeup);
source1 :包含一個(gè) mach_port 和一個(gè)回調(diào),被用于通過(guò)內(nèi)核和其他線程發(fā)送的消息,能主動(dòng)喚醒runloop。
CFRunLoopObserverRef:監(jiān)聽runloop狀態(tài),接收回調(diào)信息(常見于自動(dòng)釋放池創(chuàng)建銷毀)
數(shù)據(jù)結(jié)構(gòu):
// Observer:order(優(yōu)先級(jí)),ativity(監(jiān)聽狀態(tài)),callout(回調(diào)函數(shù))
CFRunLoopObserver {order = ..., activities = ..., callout = ...}
創(chuàng)建與添加;
// 第一個(gè)參數(shù)用于分配該observer對(duì)象的內(nèi)存空間
// 第二個(gè)參數(shù)用以設(shè)置該observer監(jiān)聽什么狀態(tài)
// 第三個(gè)參數(shù)用于標(biāo)識(shí)該observer是在第一次進(jìn)入run loop時(shí)執(zhí)行還是每次進(jìn)入run loop處理時(shí)均執(zhí)行
// 第四個(gè)參數(shù)用于設(shè)置該observer的優(yōu)先級(jí),一般為0
// 第五個(gè)參數(shù)用于設(shè)置該observer的回調(diào)函數(shù)
// 第六個(gè)參數(shù)observer的運(yùn)行狀態(tài)
CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// 執(zhí)行代碼
}
監(jiān)聽的狀態(tài):
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
};
3、Runloop內(nèi)部邏輯:關(guān)鍵在兩個(gè)判斷點(diǎn)(是否睡覺,是否退出)
代碼實(shí)現(xiàn):
// RunLoop的實(shí)現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
// 0.1 根據(jù)modeName找到對(duì)應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
// 0.2 如果mode里沒有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
// 1.1 通知 Observers: RunLoop 即將進(jìn)入 loop。---(OB會(huì)創(chuàng)建釋放池)
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
// 1.2 內(nèi)部函數(shù),進(jìn)入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
// 2.1 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 2.2 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
// 2.3 RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
// 2.4 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
// 3.1 如果沒有待處理消息,通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。--- (OB會(huì)銷毀釋放池并建立新釋放池)
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// 3.2. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
// - 一個(gè)基于 port 的Source1 的事件。
// - 一個(gè) Timer 到時(shí)間了
// - RunLoop 啟動(dòng)時(shí)設(shè)置的最大超時(shí)時(shí)間到了
// - 被手動(dòng)喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
// 3.3. 被喚醒,通知 Observers: RunLoop 的線程剛剛被喚醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
// 4.0 處理消息。
handle_msg:
// 4.1 如果消息是Timer類型,觸發(fā)這個(gè)Timer的回調(diào)。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
// 4.2 如果消息是dispatch到main_queue的block,執(zhí)行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
// 4.3 如果消息是Source1類型,處理這個(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);
// 5.1 如果處理事件完畢,啟動(dòng)Runloop時(shí)設(shè)置參數(shù)為一次性執(zhí)行,設(shè)置while參數(shù)退出Runloop
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
// 5.2 如果啟動(dòng)Runloop時(shí)設(shè)置的最大運(yùn)轉(zhuǎn)時(shí)間到期,設(shè)置while參數(shù)退出Runloop
} else if (timeout) {
retVal = kCFRunLoopRunTimedOut;
// 5.3 如果啟動(dòng)Runloop被外部調(diào)用強(qiáng)制停止,設(shè)置while參數(shù)退出Runloop
} else if (__CFRunLoopIsStopped(runloop)) {
retVal = kCFRunLoopRunStopped;
// 5.4 如果啟動(dòng)Runloop的modeItems為空,設(shè)置while參數(shù)退出Runloop
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
retVal = kCFRunLoopRunFinished;
}
// 5.5 如果沒超時(shí),mode里沒空,loop也沒被停止,那繼續(xù)loop,回到第2步循環(huán)。
} while (retVal == 0);
}
// 6. 如果第6步判斷后loop退出,通知 Observers: RunLoop 退出。--- (OB會(huì)銷毀新釋放池)
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
函數(shù)作用棧顯示:
{
// 1.1 通知Observers,即將進(jìn)入RunLoop
// 此處有Observer會(huì)創(chuàng)建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
// 2.1 通知 Observers: 即將觸發(fā) Timer 回調(diào)。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
// 2.2 通知 Observers: 即將觸發(fā) Source (非基于port的,Source0) 回調(diào)。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
// 執(zhí)行Block
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
// 2.3 觸發(fā) Source0 (非基于port的) 回調(diào)。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
// 執(zhí)行Block
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
// 3.1 通知Observers,即將進(jìn)入休眠
// 此處有Observer釋放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
// 3.2 sleep to wait msg.
mach_msg() -> mach_msg_trap();
// 3.3 通知Observers,線程被喚醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
// 4.1 如果是被Timer喚醒的,回調(diào)Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
// 4.2 如果是被dispatch喚醒的,執(zhí)行所有調(diào)用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
// 4.3 如果如果Runloop是被 Source1 (基于port的) 的事件喚醒了,處理這個(gè)事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
// 5. 退出判斷函數(shù)調(diào)用棧無(wú)顯示
} while (...);
// 6. 通知Observers,即將退出RunLoop
// 此處有Observer釋放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
一步一步寫具體的實(shí)現(xiàn)邏輯過(guò)于繁瑣不便理解,按Runloop狀態(tài)大致分為:
1- Entry:通知OB(創(chuàng)建pool);
2- 執(zhí)行階段:按順序通知OB并執(zhí)行timer,source0;若有source1執(zhí)行source1;
3- 休眠階段:利用mach_msg判斷進(jìn)入休眠,通知OB(pool的銷毀重建);被消息喚醒通知OB;
4- 執(zhí)行階段:按消息類型處理事件;
5- 判斷退出條件:如果符合退出條件(一次性執(zhí)行,超時(shí),強(qiáng)制停止,modeItem為空)則退出,否則回到第2階段;
6- Exit:通知OB(銷毀pool)。
4、Runloop本質(zhì):mach port和mach_msg()。
Mach是XNU的內(nèi)核,進(jìn)程、線程和虛擬內(nèi)存等對(duì)象通過(guò)端口發(fā)消息進(jìn)行通信,Runloop通過(guò)mach_msg()函數(shù)發(fā)送消息,如果沒有port 消息,內(nèi)核會(huì)將線程置于等待狀態(tài) mach_msg_trap() 。如果有消息,判斷消息類型處理事件,并通過(guò)modeItem的callback回調(diào)(處理事件的具體執(zhí)行是在DoBlock里還是在回調(diào)里目前我還不太明白???)。
Runloop有兩個(gè)關(guān)鍵判斷點(diǎn),一個(gè)是通過(guò)msg決定Runloop是否等待,一個(gè)是通過(guò)判斷退出條件來(lái)決定Runloop是否循環(huán)。
5、如何處理界面刷新:
當(dāng)在操作 UI 時(shí),比如改變了 Frame、更新了 UIView/CALayer 的層次時(shí),或者手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,這個(gè) UIView/CALayer 就被標(biāo)記為待處理,并被提交到一個(gè)全局的容器去。
蘋果注冊(cè)了一個(gè) Observer 監(jiān)聽 BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件,回調(diào)去執(zhí)行一個(gè)很長(zhǎng)的函數(shù):
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整,并更新 UI 界面。
這個(gè)函數(shù)內(nèi)部的調(diào)用棧大概是這樣的:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
QuartzCore:CA::Transaction::observer_callback:
CA::Transaction::commit();
CA::Context::commit_transaction();
CA::Layer::layout_and_display_if_needed();
CA::Layer::layout_if_needed();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA::Layer::display_if_needed();
[CALayer display];
[UIView drawRect];
6、如何處理UI事件響應(yīng)
蘋果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來(lái)接收系統(tǒng)事件,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進(jìn)程。隨后蘋果注冊(cè)的那個(gè) Source1 就會(huì)觸發(fā)回調(diào),并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue() 會(huì)把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā),其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等。通常事件比如 UIButton 點(diǎn)擊、touchesBegin/Move/End/Cancel 事件都是在這個(gè)回調(diào)中完成的。
【舉例】拿按鈕點(diǎn)擊來(lái)說(shuō)在Main thread堆棧中所處位置,堆棧最底層大概是,往上依次是,如圖:
5 Handle Touch Event
4 RunLoop(包含CFRunLoopRunSpecific,CFRunLoopRun,__CFRunLoopDoSouces0,__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION)
3 GSEventRunModal(Graphic Services)
2 UIApplication(main.m)
1 main
0 start(dyld)

注意看runloop的方法:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
7、如何處理手勢(shì)
當(dāng)上面的 _UIApplicationHandleEventQueue() 識(shí)別了一個(gè)手勢(shì)時(shí),其首先會(huì)調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對(duì)應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理。蘋果注冊(cè)了一個(gè) Observer 監(jiān)測(cè) BeforeWaiting (Loop即將進(jìn)入休眠) 事件,這個(gè)Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會(huì)獲取所有剛被標(biāo)記為待處理的 GestureRecognizer,并執(zhí)行GestureRecognizer的回調(diào)。
當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí),這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。

8、如何處理GCD任務(wù)
當(dāng)調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時(shí),libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息,RunLoop會(huì)被喚醒,并從消息中取得這個(gè) block,并在回調(diào)里執(zhí)行這個(gè) block。Runloop只處理主線程的block,dispatch 到其他線程仍然是由 libDispatch 處理的。

注意看runloop的方法:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
9、如何處理timer:(與CADisplayLink)
NSTimer 其實(shí)就是 CFRunLoopTimerRef,他們之間是 toll-free bridged 的。一個(gè) NSTimer 注冊(cè)到 RunLoop 后,RunLoop 會(huì)為其重復(fù)的時(shí)間點(diǎn)注冊(cè)好事件。例如 10:00, 10:10, 10:20 這幾個(gè)時(shí)間點(diǎn)。RunLoop為了節(jié)省資源,并不會(huì)在非常準(zhǔn)確的時(shí)間點(diǎn)回調(diào)這個(gè)Timer。Timer 有個(gè)屬性叫做 Tolerance (寬容度),標(biāo)示了當(dāng)時(shí)間點(diǎn)到后,容許有多少最大誤差。
如果某個(gè)時(shí)間點(diǎn)被錯(cuò)過(guò)了,例如執(zhí)行了一個(gè)很長(zhǎng)的任務(wù),則那個(gè)時(shí)間點(diǎn)的回調(diào)也會(huì)跳過(guò)去,不會(huì)延后執(zhí)行。就比如等公交,如果 10:10 時(shí)我忙著玩手機(jī)錯(cuò)過(guò)了那個(gè)點(diǎn)的公交,那我只能等 10:20 這一趟了。
CFRunLoopTimerRef:系統(tǒng)內(nèi)“定時(shí)鬧鐘”
NSTimer和performSEL方法實(shí)際上是對(duì)CFRunloopTimerRef的封裝;runloop啟動(dòng)時(shí)設(shè)置的最大超時(shí)時(shí)間實(shí)際上是GCD的dispatch_source_t類型。
數(shù)據(jù)結(jié)構(gòu):
// Timer:interval:(鬧鐘間隔), tolerance:(延期時(shí)間容忍度),callout(回調(diào)函數(shù))
CFRunLoopTimer {firing =..., interval = ...,tolerance = ...,next fire date = ...,callout = ...}
創(chuàng)建與生效;
//NSTimer:
// 創(chuàng)建一個(gè)定時(shí)器(需要手動(dòng)加到runloop的mode中)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// 默認(rèn)已經(jīng)添加到主線程的runLoop的DefaultMode中
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// performSEL方法
// 內(nèi)部會(huì)創(chuàng)建一個(gè)Timer到當(dāng)前線程的runloop中(如果當(dāng)前線程沒runloop則方法無(wú)效;performSelector:onThread: 方法放到指定線程runloop中)
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
相關(guān)類型(GCD的timer與CADisplayLink)
GCD的timer:
dispatch_source_t 類型,可以精確的參數(shù),不用以來(lái)runloop和mode,性能消耗更小。
dispatch_source_set_timer(dispatch_source_t source, // 定時(shí)器對(duì)象
dispatch_time_t start, // 定時(shí)器開始執(zhí)行的時(shí)間
uint64_t interval, // 定時(shí)器的間隔時(shí)間
uint64_t leeway // 定時(shí)器的精度
);
CADisplayLink 是一個(gè)和屏幕刷新率一致的定時(shí)器(但實(shí)際實(shí)現(xiàn)原理更復(fù)雜,和 NSTimer 并不一樣,其內(nèi)部實(shí)際是操作了一個(gè) Source)。如果在兩次屏幕刷新之間執(zhí)行了一個(gè)長(zhǎng)任務(wù),那其中就會(huì)有一幀被跳過(guò)去(和 NSTimer 相似),造成界面卡頓的感覺。在快速滑動(dòng)TableView時(shí),即使一幀的卡頓也會(huì)讓用戶有所察覺。Facebook 開源的 AsyncDisplayLink 就是為了解決界面卡頓的問(wèn)題,其內(nèi)部也用到了 RunLoop。
CADisplayLink :
Timer的tolerance表示最大延期時(shí)間,如果因?yàn)樽枞e(cuò)過(guò)了這個(gè)時(shí)間精度,這個(gè)時(shí)間點(diǎn)的回調(diào)也會(huì)跳過(guò)去,不會(huì)延后執(zhí)行。
CADisplayLink 是一個(gè)和屏幕刷新率一致的定時(shí)器,如果在兩次屏幕刷新之間執(zhí)行了一個(gè)長(zhǎng)任務(wù),那其中就會(huì)有一幀被跳過(guò)去(和 NSTimer 相似,只是沒有tolerance容忍時(shí)間),造成界面卡頓的感覺。
10、如何處理performSelector
除了基于端口的源,Cocoa定義了自定義輸入源,允許你在任何線程執(zhí)行selector方法。和基于端口的源一樣,執(zhí)行selector請(qǐng)求會(huì)在目標(biāo)線程上序列化,減緩許多在線程上允許多個(gè)方法容易引起的同步問(wèn)題。不像基于端口的源,一個(gè)selector執(zhí)行完后會(huì)自動(dòng)從run loop里面移除。
當(dāng)在其他線程上面執(zhí)行selector時(shí),目標(biāo)線程須有一個(gè)活動(dòng)的run loop。對(duì)于你創(chuàng)建的線程,這意味著線程在你顯式的啟動(dòng)run loop之前是不會(huì)執(zhí)行selector方法的,而是一直處于休眠狀態(tài)。
NSObject類提供了類似如下的selector方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;

注意看runloop的方法:cfrunloop_is_calling_out_to_a_timer_callback_function
11、如何處理網(wǎng)絡(luò)請(qǐng)求
關(guān)于網(wǎng)絡(luò)請(qǐng)求的接口:最底層是CFSocket層,然后是CFNetwork將其封裝,然后是NSURLConnection對(duì)CFNetwork進(jìn)行面向?qū)ο蟮姆庋b,NSURLSession 是 iOS7 中新增的接口,也用到NSURLConnection的loader線程。所以還是以NSURLConnection為例。
當(dāng)開始網(wǎng)絡(luò)傳輸時(shí),NSURLConnection 創(chuàng)建了兩個(gè)新線程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private。其中 CFSocket 線程是處理底層 socket 連接的。NSURLConnectionLoader 這個(gè)線程內(nèi)部會(huì)使用 RunLoop 來(lái)接收底層 socket 的事件,并通過(guò)之前添加的 Source0 通知到上層的 Delegate。

12、Runloop 常見應(yīng)用:
根據(jù)前面介紹過(guò)的跟runloop先關(guān)的模塊,那么每個(gè)模塊都有應(yīng)用。這里列舉常見應(yīng)用:
滑動(dòng)與圖片刷新
當(dāng)tableview的cell上有需要從網(wǎng)絡(luò)獲取的圖片的時(shí)候,滾動(dòng)tableView,異步線程會(huì)去加載圖片,加載完成后主線程就會(huì)設(shè)置cell的圖片,但是會(huì)造成卡頓??梢宰屧O(shè)置圖片的任務(wù)在CFRunLoopDefaultMode下進(jìn)行,當(dāng)滾動(dòng)tableView的時(shí)候,RunLoop是在 UITrackingRunLoopMode 下進(jìn)行,不去設(shè)置圖片,而是當(dāng)停止的時(shí)候,再去設(shè)置圖片。
- (void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下執(zhí)行(刷新圖片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];
}
常駐子線程,保持子線程一直處理事件
為了保證線程長(zhǎng)期運(yùn)轉(zhuǎn),可以在子線程中加入RunLoop,并且給Runloop設(shè)置item,防止Runloop自動(dòng)退出。
+ (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;
}
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
接到程序崩潰時(shí)的信號(hào)進(jìn)行自主處理例如彈出提示等
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
for (NSString *mode in allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
異步測(cè)試
- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
__block Boolean fulfilled = NO;
void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
fulfilled = block();
if (fulfilled) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
};
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// Run!
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
return fulfilled;
}
Run loop的優(yōu)點(diǎn)
一個(gè)run loop就是一個(gè)事件處理循環(huán),用來(lái)不停的監(jiān)聽和處理輸入事件并將其分配到對(duì)應(yīng)的目標(biāo)上進(jìn)行處理。如果僅僅是想實(shí)現(xiàn)這個(gè)功能,你可能會(huì)想一個(gè)簡(jiǎn)單的while循環(huán)不就可以實(shí)現(xiàn)了嗎,用得著費(fèi)老大勁來(lái)做個(gè)那么復(fù)雜的機(jī)制?顯然,蘋果的架構(gòu)設(shè)計(jì)師不是吃干飯的,你想到的他們?cè)缇拖脒^(guò)了。
首先,NSRunLoop是一種更加高明的消息處理模式,他就高明在對(duì)消息處理過(guò)程進(jìn)行了更好的抽象和封裝,這樣才能是的你不用處理一些很瑣碎很低層次的具體消息的處理,在NSRunLoop中每一個(gè)消息就被打包在input source或者是timer source(見后文)中了。
其次,也是很重要的一點(diǎn),使用run loop可以使你的線程在有工作的時(shí)候工作,沒有工作的時(shí)候休眠,這可以大大節(jié)省系統(tǒng)資源。
什么時(shí)候使用run loop
僅當(dāng)在為你的程序創(chuàng)建輔助線程的時(shí)候,你才需要顯式運(yùn)行一個(gè)run loop。Run loop是程序主線程基礎(chǔ)設(shè)施的關(guān)鍵部分。所以,Cocoa和Carbon程序提供了代碼運(yùn)行主程序的循環(huán)并自動(dòng)啟動(dòng)run loop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)作為程序啟動(dòng)步驟的一部分,它在程序正常啟動(dòng)的時(shí)候就會(huì)啟動(dòng)程序的主循環(huán)。類似的,RunApplicationEventLoop函數(shù)為Carbon程序啟動(dòng)主循環(huán)。如果你使用xcode提供的模板創(chuàng)建你的程序,那你永遠(yuǎn)不需要自己去顯式的調(diào)用這些例程。
原文轉(zhuǎn)載鏈接:https://blog.csdn.net/hherima/article/details/51746125
參考http://www.starming.com/index.php?v=index&view=74
http://www.itdecent.cn/p/37ab0397fec7
http://blog.csdn.net/ztp800201/article/details/9240913
http://www.cnblogs.com/zy1987/p/4582466.html