什么是Runloop
- 為了實(shí)現(xiàn)線程能在有事件喚起的時(shí)候?qū)崟r(shí)處理Event,并且在沒(méi)有事件的時(shí)候進(jìn)入休眠并不退出贮聂,繼續(xù)等待下一次事件消息的喚醒的機(jī)制帐我,所以出現(xiàn)了和很多系統(tǒng)中Eventloop機(jī)制類(lèi)似的Runloop機(jī)制。
Runloop的實(shí)質(zhì)
- RunLoop 實(shí)際上就是一個(gè)對(duì)象,這個(gè)對(duì)象管理了其需要處理的事件和消息扒袖,并提供了一個(gè)入口函數(shù)來(lái)執(zhí)行上面 Event Loop 的邏輯。線程執(zhí)行了這個(gè)函數(shù)后亩码,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 “接受消息->等待->處理” 的循環(huán)中季率,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回描沟。
Runloops 的功能
- 使程序一直運(yùn)行并接受用戶(hù)輸入
- 決定程序在何時(shí)應(yīng)該處理哪些Event
- 調(diào)用解耦 (SmallTalk/Message Queue)
- 節(jié)省CPU時(shí)間
Runloops的Cocoa實(shí)現(xiàn)
- Foundation 中實(shí)現(xiàn)了 NSRunloop 框架
- NSRunloop 基于 CFRunloop封裝而來(lái)飒泻,CFRunloop基于c/c++封裝,代碼開(kāi)源
- 支持CFRunloop運(yùn)行的核心內(nèi)容有 GCD, XNU的mach內(nèi)核吏廉,block回調(diào)機(jī)制泞遗,pthread等
- 依賴(lài)Runloop運(yùn)行的框架、類(lèi)席覆、方法:NSTimer, UIEvent, Autorelease Pool, performThread和 performDelay等史辙,CADisplayLink,CATransition佩伤,CAAnimation,dispatch_get_main_queue(),NSURLConnection,AFNetworking,
Runloop 的 類(lèi)結(jié)構(gòu)分析
1.CFRunLoopSourceRef
- Source 是 RunLoop的數(shù)據(jù)抽象類(lèi) (protocol)
- Runloop定義了兩個(gè)Version的Source(事件產(chǎn)生的地方):
- Source0: 基于非Port的事件,只包含了一個(gè)回調(diào)(函數(shù)指針)聊倔,它并不能主動(dòng)觸發(fā)事件。一般是App自己負(fù)責(zé)管理及觸發(fā)如UIEvent/CFSocket生巡。使用時(shí)耙蔑,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理孤荣,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來(lái)喚醒 RunLoop甸陌,讓其處理這個(gè)事件。
- Source1: 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針)垃环,被用于通過(guò)內(nèi)核和其他線程相互發(fā)送消息.由RunLoop和內(nèi)核管理邀层,Mach port驅(qū)動(dòng),如CFMachPort遂庄、CFMessagePort
2.CFRunloopTimerRef
基于時(shí)間的觸發(fā)器寥院,它和 NSTimer 是toll-free bridged 的,可以混用涛目。其包含一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào)(函數(shù)指針)秸谢。當(dāng)其加入到 RunLoop 時(shí)凛澎,RunLoop會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí)估蹄,RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)塑煎。
3.CFRunloopObserverRef
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
};
RUNLoopObserver 與 AutoreleasePool
AutoreleasePool
App啟動(dòng)后,蘋(píng)果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer臭蚁,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()最铁。第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(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)之后。
RunloopMode
- Runloop 在同一段時(shí)間只能且必須在一種特定的mode下Run
- 更換Mode時(shí)衬浑,必須Stop當(dāng)前Loop捌浩,重齊新Loop
1.NSDefaulyRunLoopMode
默認(rèn)狀態(tài)、空閑狀態(tài)
2.UITrackingRunLoopMode
滑動(dòng)態(tài)mode -追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)
3.UIInitializationRunLoopMode
私有嚎卫,啟動(dòng)APP時(shí)切換進(jìn)入嘉栓,完成啟動(dòng)就被拋棄不再使用
4.GSEventReceiveRunLoopMode
接受系統(tǒng)事件的內(nèi)部Mode,通常用不到拓诸。
5.NSRunLoopCommonModes
這個(gè)mode比較特殊侵佃,包含了DefaultMode和UITrackingRunLoopMode 的狀態(tài),一般作占位用奠支,結(jié)構(gòu)也比較特殊
應(yīng)用: UITrakingRunloopMode 與 Timer
主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode馋辈。這兩個(gè) Mode 都已經(jīng)被標(biāo)記為”Common”屬性。DefaultMode 是 App 平時(shí)所處的狀態(tài)倍谜,TrackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)迈螟。當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí),Timer 會(huì)得到重復(fù)回調(diào)尔崔,但此時(shí)滑動(dòng)一個(gè)TableView時(shí)答毫,RunLoop 會(huì)將 mode 切換為 TrackingRunLoopMode,這時(shí) Timer 就不會(huì)被回調(diào)季春,并且也不會(huì)影響到滑動(dòng)操作洗搂。
// 默認(rèn)添加到DefaultMode中
NSTimer *timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
// 添加到CommonModes中去
NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject4 selector:@selector(timerAction:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
RunLoopMode的切換
- Run RunLoop對(duì)象需要每次調(diào)用int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle)需要指定一個(gè)ModeName進(jìn)入
- 當(dāng)有滑動(dòng)事件響應(yīng)后,主線程的RunLoop對(duì)象會(huì)喚醒RunLoop對(duì)象,調(diào)用主函數(shù)傳入TrackingMode的Name切換到滑動(dòng)Mode中處理相應(yīng)的Source
Runloop 運(yùn)行流程
/// 用DefaultMode啟動(dòng)
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動(dòng)耘拇,允許設(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找到對(duì)應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒(méi)有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)用者手動(dòng)喚醒
__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ù)說(shuō)處理完事件就返回溯街。
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è)都沒(méi)有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒(méi)超時(shí),mode里沒(méi)空洋丐,loop也沒(méi)被停止呈昔,那繼續(xù)loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出友绝。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
- 掛起
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息堤尾。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
/// ? 一個(gè)基于 port 的Source 的事件迁客。
/// ? 一個(gè) Timer 到時(shí)間了
/// ? RunLoop 自身的超時(shí)時(shí)間到了
/// ? 被其他什么調(diào)用者手動(dòng)喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
上面的函數(shù)主要實(shí)現(xiàn)了兩個(gè)步驟
1.指定用于喚醒port的端口
2.調(diào)用mach_msg監(jiān)聽(tīng)喚醒端口郭宝,被喚醒前,將這個(gè)線程掛起掷漱,停留在mach_msg_trap狀態(tài)
- 喚醒
mach_msg(msg,MACH_RCV_MSG,port)
向指定端口發(fā)送port消息粘室,喚醒Runloop
應(yīng)用
tableViewCell 優(yōu)化
// 當(dāng)滑動(dòng)狀態(tài)下就不進(jìn)行設(shè)置圖片操作
UIImage *download = ...;
[self.avatarImageView perfomSelector:@selector(setImage:) withObject:download afterDelay:0 inModes:@[NSDefaultRunloopMode]];
signal救回
CFRunloopRef runloop = CFRunloopGetCurrent();
NSArray *allModes = CFBridgingRelease(XFRunLoopCopyAllModes(runloop));
while(1){
for (NString *mode in allModes) {
CFRunloopRunInMode((CFStringRef)mode, 0.001, false);
}
}
可以做最后的收集挽回工作(彈窗提示等)
此處后面在崩潰信息處補(bǔ)充
一次觸摸事件的回應(yīng)
具體流程參照應(yīng)用 -- 圖形響應(yīng)鏈
1.蘋(píng)果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來(lái)接收系統(tǒng)事件,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()
- 當(dāng)我們觸發(fā)了事件(觸摸/鎖屏/搖晃等)后卜范,由IOKit.framework生成一個(gè) IOHIDEvent事件
(IOKit是蘋(píng)果的硬件驅(qū)動(dòng)框架衔统,它專(zhuān)門(mén)處理用戶(hù)交互設(shè)備,由IOHIDServices和IOHIDDisplays兩部分組成,其中IOHIDServices是專(zhuān)門(mén)處理用戶(hù)交互的缰冤,它會(huì)將事件封裝成IOHIDEvents對(duì)象)
- 然后這些事件又由SpringBoard接收犬缨,它只接收收按鍵(鎖屏/靜音等),觸摸棉浸,加速怀薛,接近傳感器等幾種 Event
- 接著用mach_port轉(zhuǎn)發(fā)給需要的App進(jìn)程.隨后蘋(píng)果注冊(cè)的那個(gè)Source1 就會(huì)接收IOHIDEvent,之后再回調(diào)__IOHIDEventSystemClientQueueCallback()迷郑,觸發(fā)一個(gè)Source0資源枝恋,Source觸發(fā)了回調(diào) _UIApplicationHandleEventQueue()進(jìn)行應(yīng)用內(nèi)部的分發(fā)。_UIApplicationHandleEventQueue()把IOHIDEvent處理包裝成UIEvent進(jìn)行處理分發(fā)嗡害,我們平時(shí)的UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow/UIButton 點(diǎn)擊焚碌、touchesBegin/Move/End/Cancel這些事件,都是在這個(gè)回調(diào)中完成
- 當(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)記為待處理。
- 蘋(píng)果注冊(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)建/銷(xiāo)毀/狀態(tài)改變)時(shí)畏线,這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。
該文的參考資料
1.深入理解RunLoop https://blog.ibireme.com/2015/05/18/runloop/
2. Runloop 線下分享 http://v-wb.youku.com/v_show/id_XODgxODkzODI0.html#paction