Runloop
什么是runloop
Runloop是通過(guò)內(nèi)部維護(hù)的事件循環(huán) 來(lái)對(duì)事件/消息進(jìn)行管理的一個(gè)對(duì)象
Event Loop:
- 沒(méi)有消息需要處理時(shí),休眠以避免資源占用 用戶態(tài)->內(nèi)核態(tài)
- 有消息需要處理時(shí),立刻被喚醒 內(nèi)核態(tài)->用戶態(tài)
runloop對(duì)象
CoreFoundation-CFRunloop
Foundation - NSRunloop
NSRunLoop實(shí)際上是CFRunLoop的高層抽象,CFRunloop是線程安全的,NSRunloop非線程安全.
CFRunloop是開(kāi)源的:https://opensource.apple.com/source/CF/CF-1151.16/
//Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
//Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
開(kāi)啟runloop
- (void)run;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
- (void)runUntilDate:(NSDate *)limitDate;
run方法對(duì)應(yīng)上面CFRunloopRef中的CFRunLoopRun并不會(huì)退出催束,除非調(diào)用CFRunLoopStop();通常如果想要永遠(yuǎn)不會(huì)退出RunLoop才會(huì)使用此方法胜嗓,否則可以使用runUntilDate。
runMode:beforeDate:則對(duì)應(yīng)CFRunLoopRunInMode(mode,limiteDate,true)方法,只執(zhí)行一次篮愉,執(zhí)行完就退出滩届;通常用于手動(dòng)控制RunLoop(例如在while循環(huán)中)集侯。
runUntilDate:方法其實(shí)是CFRunLoopRunInMode(kCFRunLoopDefaultMode,limiteDate,false),執(zhí)行完并不會(huì)退出帜消,繼續(xù)下一次RunLoop直到timeout棠枉。
runloop與線程
一般來(lái)說(shuō),線程同一時(shí)間只能執(zhí)行一個(gè)任務(wù),任務(wù)執(zhí)行完畢線程就會(huì)被銷(xiāo)毀.每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象。RunLoop 和線程的一一對(duì)應(yīng)關(guān)系保存一個(gè)全局 Dictionary 里 __CFRunLoops.主線程的RunLoop是自動(dòng)創(chuàng)建的(使主線程不被銷(xiāo)毀保證了程序的運(yùn)行而不退出)泡挺,子線程的RunLoop需要手動(dòng)創(chuàng)建CFRunLoopGetCurrent(),在第一次獲取時(shí)創(chuàng)建辈讶,在線程結(jié)束時(shí)銷(xiāo)毀。蘋(píng)果未提供手動(dòng)創(chuàng)建runloop的API,
在UIApplicationMain函數(shù)中娄猫,實(shí)際就是開(kāi)啟了一個(gè)和主線程相關(guān)的RunLoop贱除,導(dǎo)致UIApplicationMain不會(huì)返回,一直在運(yùn)行中媳溺,也就保證了程序的持續(xù)運(yùn)行月幌。
Runloop機(jī)制
Core Foundation中關(guān)于RunLoop的5個(gè)類(lèi):
- CFRunLoopRef //獲得當(dāng)前RunLoop和主RunLoop
- CFRunLoopModeRef //運(yùn)行模式,只能選擇一種悬蔽,在不同模式中做不同的操作,且運(yùn)行必須有指定的Mode
- CFRunLoopSourceRef //事件源扯躺,輸入源
- CFRunLoopTimerRef //定時(shí)器時(shí)間
- CFRunLoopObserverRef //觀察者
CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //和線程一對(duì)一的關(guān)系
uint32_t _winthread;
CFMutableSetRef _commonModes; // 字符串,記錄所有標(biāo)記為common的mode
CFMutableSetRef _commonModeItems; // 所有commonMode的item(source蝎困、timer录语、observer)
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes; // CFRunLoopModeRef set
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
? CFRunLoop 里面包含了線程,若干個(gè) mode禾乘。
? CFRunLoop 和線程是一一對(duì)應(yīng)的澎埠。
? _blocks_head 是 perform block 加入到里面的
CFRunLoopMode
// 定義 CFRunLoopModeRef 為指向 __CFRunLoopMode 結(jié)構(gòu)體的指針
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0; // source0 set ,非基于Port的始藕,接收點(diǎn)擊事件蒲稳,觸摸事件等APP 內(nèi)部事件
CFMutableSetRef _sources1; // source1 set氮趋,基于Port的,通過(guò)內(nèi)核和其他線程通信弟塞,接收凭峡,分發(fā)系統(tǒng)事件
CFMutableArrayRef _observers; // observer 數(shù)組
CFMutableArrayRef _timers; // timer 數(shù)組
CFMutableDictionaryRef _portToV1SourceMap;// source1 對(duì)應(yīng)的端口號(hào)
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
通過(guò)CFRunloopRef對(duì)應(yīng)結(jié)構(gòu)體的定義可以知道每種Runloop都包含若干個(gè)(1:N)Mode,每個(gè)Mode又包含若干(1:N)Source/Timer/Observer决记。Runloop總是運(yùn)行在某種特定的CFRunLoopModeRef下,當(dāng)切換Mode時(shí)必須退出當(dāng)前Mode,然后重新進(jìn)入Runloop以保證不同Mode的Source/Timer/Observer互不影響倍踪。
系統(tǒng)默認(rèn)提供的Run Loop Modes有kCFRunLoopDefaultMode(NSDefaultRunLoopMode)和UITrackingRunLoopMode系宫,需要切換到對(duì)應(yīng)的Mode時(shí)只需要傳入對(duì)應(yīng)的名稱(chēng)即可。前者是系統(tǒng)默認(rèn)的Runloop Mode建车,例如進(jìn)入iOS程序默認(rèn)不做任何操作就處于這種Mode中扩借,此時(shí)滑動(dòng)UIScrollView,主線程就切換Runloop到到UITrackingRunLoopMode缤至,不再接受其他事件操作(除非你將其他Source/Timer設(shè)置到UITrackingRunLoopMode下)潮罪。
kCFRunLoopCommonModes(NSRunLoopCommonModes),這個(gè)并不是某種具體的Mode,而是一種模式組合领斥,在iOS系統(tǒng)中默認(rèn)包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode,是同步Source,Timer,Observer到多個(gè)Mode中的一種技術(shù)方案(注意:并不是說(shuō)Runloop會(huì)運(yùn)行在kCFRunLoopCommonModes這種模式下嫉到,而是相當(dāng)于分別注冊(cè)了 NSDefaultRunLoopMode和 UITrackingRunLoopMode。當(dāng)然你也可以通過(guò)調(diào)用CFRunLoopAddCommonMode()方法將自定義Mode放到 kCFRunLoopCommonModes組合)月洛。
RunLoopSource
RunLoopSource 分為 source0 和 source1何恶。
- source0 是非基于 port 的事件,主要是 APP 內(nèi)部事件嚼黔,如點(diǎn)擊事件细层,觸摸事件等 需要手動(dòng)喚醒線程。
- source1 是基于Port的唬涧,通過(guò)內(nèi)核和其他線程通信疫赎,接收,分發(fā)系統(tǒng)事件碎节。具備喚醒線程的能力
- CFRunLoopSource 里面包含一個(gè) _runLoops捧搞,也就意味著一個(gè) CFRunLoopSource 可以被添加到多個(gè) runloop mode 中去。
- Source1在處理的時(shí)候會(huì)分發(fā)一些操作給Source0去處理
Source0(負(fù)責(zé)App內(nèi)部事件钓株,由App負(fù)責(zé)管理觸發(fā)实牡,例如UITouch事件)和Timer(又叫Timer Source,基于時(shí)間的觸發(fā)器轴合,上層對(duì)應(yīng)NSTimer)是兩個(gè)不同的Runloop事件源(當(dāng)然Source0是Input Source中的一類(lèi)创坞,Input Source還包括Custom Input Source,由其他線程手動(dòng)發(fā)出)受葛,RunLoop被這些事件喚醒之后就會(huì)處理并調(diào)用事件處理方法(CFRunLoopTimerRef的回調(diào)指針和CFRunLoopSourceRef均包含對(duì)應(yīng)的回調(diào)指針)题涨。
Source1除了包含回調(diào)指針外包含一個(gè)mach port偎谁,和Source0需要手動(dòng)觸發(fā)不同,Source1可以監(jiān)聽(tīng)系統(tǒng)端口和其他線程相互發(fā)送消息纲堵,它能夠主動(dòng)喚醒RunLoop(由操作系統(tǒng)內(nèi)核進(jìn)行管理巡雨,例如CFMessagePort消息)。官方也指出可以自定義Source席函,因此對(duì)于CFRunLoopSourceRef來(lái)說(shuō)它更像一種協(xié)議铐望,框架已經(jīng)默認(rèn)定義了兩種實(shí)現(xiàn),如果有必要開(kāi)發(fā)人員也可以自定義.
RunLoopTimer
- CFRunLoopTimer 是基于事件的定時(shí)器茂附,可以在設(shè)定的時(shí)間點(diǎn)拋出回調(diào)
- CFRunLoopTimer和NSTimer是toll-free bridged的正蛙,可以相互轉(zhuǎn)換。
CFRunLoopObserver
觀察者营曼,可以觀察RunLoop的各種狀態(tài)乒验,并拋出回調(diào)〉仝澹可以監(jiān)聽(tīng)的狀態(tài)如下:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將開(kāi)始Timer處理
kCFRunLoopBeforeSources = (1UL << 2), // 即將開(kāi)始Source處理
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //從休眠狀態(tài)喚醒
kCFRunLoopExit = (1UL << 7), //退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU //所有狀態(tài)
};
我們可以手動(dòng)創(chuàng)建一個(gè)CFRunLoopObserver來(lái)監(jiān)聽(tīng)Runloop的各種狀態(tài)
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFStringRef runLoopMode = kCFRunLoopDefaultMode;
//參數(shù)1:構(gòu)造器
//參數(shù)2:監(jiān)聽(tīng)的CFRunLoopActivity(位移型枚舉)活動(dòng)狀態(tài),kCFRunLoopAllActivities代表所有狀態(tài)
//參數(shù)3:是否每次都需要監(jiān)聽(tīng)
//參數(shù)4:優(yōu)先級(jí)
//參數(shù)5:回調(diào)函數(shù)
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即將進(jìn)入 RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理 Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理 Source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將進(jìn)入休眠");
;
break;
case kCFRunLoopAfterWaiting:
NSLog(@"從休眠中喚醒");
break;
case kCFRunLoopExit:
NSLog(@"即將退出Loop");
break;
default:
break;
}
});
//添加observer到runloop對(duì)象,來(lái)監(jiān)聽(tīng)指定的Mode
CFRunLoopAddObserver(runLoop, observer, runLoopMode);
CFRelease(observer);
我們經(jīng)扯腿可以在調(diào)用堆棧看到以下函數(shù)
//Observer回調(diào)
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
//CFRunLoopPerformBlock回調(diào)
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
//dispatch_getMain
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
//timer,NSTimer...
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
//source0比如點(diǎn)擊事件
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
{
/// 1. 通知Observers录煤,即將進(jìn)入RunLoop
/// 此處有Observer會(huì)創(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__(source0);
__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);
}
CFRunLoopRun
/// 用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);
}
參考:
https://blog.ibireme.com/2015/05/18/runloop/
https://www.cnblogs.com/kenshincui/p/6823841.html
http://honglu.me/2017/03/30/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3RunLoop/
http://www.imlifengfeng.com/blog/?p=487