原文的介紹http://www.cocoachina.com/ios/20150601/11970.html
1.runloop是來(lái)做什么的碉输?runloop和線程有什么關(guān)系?主線程默認(rèn)開(kāi)啟了runloop么啦扬?子線程呢类缤?
runloop:這是個(gè)對(duì)象在程序運(yùn)行過(guò)程中通過(guò)循環(huán)跑圈來(lái)處理線程里面的事件和消息恭垦,從而保持程序的持續(xù)運(yùn)行圈匆;而且在沒(méi)有事件處理的時(shí)候漠另,會(huì)進(jìn)入睡眠模式,從而節(jié)省CPU資源跃赚,提高程序性能
runloop和線程的關(guān)系:線程在處理完自己的任務(wù)后一般會(huì)退出笆搓,為了實(shí)現(xiàn)線程不退出能夠隨時(shí)處理任務(wù),不被釋放纬傲,就必須有一個(gè)runloop來(lái)不停的跑圈满败;線程和runloop是一一對(duì)應(yīng)的,關(guān)系保存在全局字典里叹括;線程創(chuàng)建時(shí)并沒(méi)有RunLoop算墨,(主線程除外),RunLoop不能創(chuàng)建汁雷,只能主動(dòng)獲取才會(huì)有净嘀。RunLoop的創(chuàng)建是在第一次獲取時(shí),RunLoop的銷毀是發(fā)生在線程結(jié)束時(shí)侠讯。只能在一個(gè)線程中獲取自己和主線程的RunLoop挖藏。
主線程默認(rèn)是開(kāi)啟一個(gè)runloop。也就是這個(gè)runloop才能保證我們程序正常的運(yùn)行继低。子線程是默認(rèn)沒(méi)有開(kāi)始runloop的
- runloop創(chuàng)建原理(與線程得關(guān)系)和內(nèi)部實(shí)現(xiàn)原理
runloop創(chuàng)建原理
// 全局的Dictionary熬苍,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 訪問(wèn) loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;
// 獲取一個(gè) pthread 對(duì)應(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);
// 注冊(cè)一個(gè)回調(diào)焙压,當(dāng)線程銷毀時(shí)鸿脓,順便也銷毀其對(duì)應(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());
}
runloop運(yùn)行原理(這種模型通常被稱作 Event Loop):
對(duì)事件源做判斷涯曲,有就通過(guò)相應(yīng)的函數(shù)返回野哭,沒(méi)有就往下執(zhí)行,直到mach.msg進(jìn)入休眠并等待喚醒幻件。mach是內(nèi)核的核心拨黔,提供了進(jìn)程間通信(以消息的形式來(lái)完成)和處理器調(diào)度等基本服務(wù),消息的發(fā)送和接收使用<mach/message.h>中的mach_msg()函數(shù)绰沥,所以runloop的喚醒和休眠是系統(tǒng)完成的篱蝇。
RunLoop是一個(gè)對(duì)象并提供了do-while循環(huán)執(zhí)行的一個(gè)入口函數(shù)贺待,當(dāng)線程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中零截,runloop在循環(huán)執(zhí)行得過(guò)程中通過(guò)CFRunLoopObserverRef監(jiān)聽(tīng)runloop狀態(tài)的改變麸塞,然后調(diào)用source/time/observer的回調(diào)函數(shù),完成事件處理涧衙;直到超時(shí)或被手動(dòng)停止哪工,該函數(shù)才會(huì)返回。
一個(gè) RunLoop 包含若干個(gè) Mode绍撞,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer正勒。每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode傻铣,這個(gè)Mode被稱作 CurrentMode章贞。如果需要切換 Mode,只能退出 Loop非洲,再重新指定一個(gè) Mode 進(jìn)入鸭限。這樣做主要是為了分隔開(kāi)不同組的 Source/Timer/Observer,讓其互不影響两踏。
source0和source1的區(qū)別:
Source0(負(fù)責(zé)App內(nèi)部事件败京,由App負(fù)責(zé)管理觸發(fā),例如UITouch事件)梦染,只包含了一個(gè)回調(diào)(函數(shù)指針)赡麦,它并不能主動(dòng)觸發(fā)事件。使用時(shí)帕识,你需要先調(diào)用 CFRunLoopSourceSignal(source)泛粹,將這個(gè) Source 標(biāo)記為待處理 signal 狀態(tài),然后手動(dòng)調(diào) CFRunLoopWakeUp(runloop) 來(lái)喚醒 RunLoop肮疗,讓其處理這個(gè)事件证薇。
Source1除了包含回調(diào)指針外包含一個(gè)mach port馏谨,Source1可以監(jiān)聽(tīng)系統(tǒng)端口和其他線程相互發(fā)送消息恕齐,它能夠主動(dòng)喚醒RunLoop(由操作系統(tǒng)內(nèi)核進(jìn)行管理踩官,例如CFMessagePort消息)。
事件:觸摸事件(用戶交互事件)碱呼、時(shí)鐘事件(timer)蒙挑、網(wǎng)絡(luò)事件、系統(tǒng)內(nèi)核事件
1愚臀, runloop得五個(gè)類
CFRunLoopRef //獲得當(dāng)前RunLoop和主RunLoop
CFRunLoopModeRef //運(yùn)行模式脆荷,只能選擇一種,在不同模式中做不同的操作
CFRunLoopSourceRef //事件源,輸入源 (是事件產(chǎn)生的地方蜓谋。)
Source0 只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件炭分。使用時(shí)桃焕,你需要先調(diào)用 CFRunLoop-SourceSignal(source),將這個(gè) Source 標(biāo)記為待處理捧毛,然后手動(dòng)調(diào)用 CFRunLoop-WakeUp(runloop) 來(lái)喚醒 RunLoop观堂,讓其處理這個(gè)事件。
Source1 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針)呀忧,被用于通過(guò)內(nèi)核和其他線程相互發(fā)送消息师痕。這種 Source 能主動(dòng)喚醒 RunLoop 的線程,其原理在下面會(huì)講到而账。
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)。
CFRunLoopObserverRef //觀察者是觀察者锯茄,每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針)厢塘,當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過(guò)回調(diào)接受到這個(gè)變化肌幽⊥砟耄可以觀測(cè)的時(shí)間點(diǎn)有以下幾個(gè):
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
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode:界面跟蹤 Mode牍颈,用于 ScrollView 追蹤觸摸滑動(dòng)迄薄,保證界面滑動(dòng)時(shí)不受其他 Mode 影響
UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode煮岁,通常用不到
kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode讥蔽,不是一種真正的Mode
滑動(dòng)UI時(shí)NSTimer不能工作:當(dāng)NSTimer運(yùn)行在NSDefaultRunLoopMode下的時(shí)候會(huì)因?yàn)镽unLoopMode的改變而無(wú)法正常工作。需要切換到NSRunLoopCommonModes才能保證NSTimer在NSDefaultRunLoopMode和UITrackingRunLoopMode下正常工作画机。
2冶伞,CFRunLoopMode 和 CFRunLoop 的結(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
...
};
runloop內(nèi)部代碼實(shí)現(xiàn)如下
在開(kāi)發(fā)過(guò)程中幾乎所有的操作都是通過(guò)Call out進(jìn)行回調(diào)的
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
/// 用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);
/// 處理非延遲的主線程調(diào)用
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)隆嗅。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks();
}
/// 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);
}
3搬瑰,舉例說(shuō)明
AutoreleasePool:App啟動(dòng)后,,系統(tǒng)在主線程RunLoop 里注冊(cè)兩個(gè)Observser,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()。
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池控硼。其優(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 優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后卡乾。
在主線程執(zhí)行的代碼,通常是寫(xiě)在諸如事件回調(diào)翼悴、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開(kāi)發(fā)者也不必顯示創(chuàng)建 Pool 了幔妨。
事件相應(yīng):蘋(píng)果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來(lái)接收系統(tǒng)事件鹦赎,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。當(dāng)事件觸發(fā)后經(jīng)過(guò)系統(tǒng)內(nèi)核一系列響應(yīng)后通過(guò)mach port轉(zhuǎn)發(fā)給需要的進(jìn)程误堡,隨后蘋(píng)果注冊(cè)的那個(gè) Source1 就會(huì)觸發(fā)回調(diào)古话,并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。然后就是找到第一響應(yīng)對(duì)象然后就是響應(yīng)锁施。
手勢(shì)識(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)記為待處理。蘋(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)建/銷毀/狀態(tài)改變)時(shí)描焰,這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。
界面更新:當(dāng)在操作 UI 時(shí)栅螟,比如改變了 Frame、更新了 UIView/CALayer 的層次時(shí)篱竭,或者手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后力图,這個(gè) UIView/CALayer 就被標(biāo)記為待處理,并被提交到一個(gè)全局的容器去掺逼。蘋(píng)果注冊(cè)了一個(gè) Observer 監(jiān)聽(tīng) 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 界面赘那。
GCD: 當(dāng)調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時(shí),libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息氯质,RunLoop會(huì)被喚醒募舟,并從消息中取得這個(gè) block,并在回調(diào) CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里執(zhí)行這個(gè) block闻察。
PerformSelecter:當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后拱礁,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒(méi)有 RunLoop辕漂,則這個(gè)方法會(huì)失效呢灶。
runloop