前言
這篇文章是兩年前在公司內(nèi)部分享時(shí)候?qū)懙那傩恚约罕容^滿意文章的成色凫佛,就分享出來(lái)旧烧。
最近在進(jìn)行社招的過(guò)程中特咆,發(fā)現(xiàn)很多iOS五年以上的老司機(jī)對(duì)Runloop機(jī)制都不能很好的理解季惩,而Runloop是iOS中非常重要的一個(gè)組件录粱,很多性能優(yōu)化腻格,都需要對(duì)Runloop機(jī)制有深刻的理解才能進(jìn)行。所以整理了以前的筆記啥繁,抽絲剝繭的分析Runloop的核心機(jī)制菜职。
當(dāng)有持續(xù)的異步任務(wù)需求時(shí),我們會(huì)創(chuàng)建一個(gè)獨(dú)立的生命周期可控的線程旗闽。RunLoop就是控制線程生命周期并接收事件進(jìn)行處理的機(jī)制酬核。是iOS、OSX開(kāi)發(fā)中比較基礎(chǔ)的一個(gè)概念适室。但它也是事件響應(yīng)與任務(wù)處理的核心機(jī)制嫡意,貫穿整個(gè)系統(tǒng)。
RunLoop 基本概念
為什么引入RunLoop機(jī)制
一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù)捣辆,執(zhí)行完成后線程就會(huì)退出蔬螟。RunLoop可以讓線程隨時(shí)處理事件但不退出。
RunLoop并不是iOS平臺(tái)的專(zhuān)屬概念汽畴,在任何平臺(tái)的多線程編程中旧巾,為控制線程的生命周期,接收處理異步消息都需要類(lèi)似RunLoop的循環(huán)機(jī)制實(shí)現(xiàn)忍些,Android的Looper就是類(lèi)似的機(jī)制鲁猩。這種模型還通常被稱(chēng)作Event Loop。Event Loop在很多系統(tǒng)和框架里都有實(shí)現(xiàn)罢坝。比如Node.js 的事件處理廓握,Windows程序的消息循環(huán)。
實(shí)現(xiàn)這種模型的關(guān)鍵點(diǎn)在于:
- 如何管理事件和消息
- 如何讓線程在沒(méi)有處理消息時(shí)休眠嘁酿,避免無(wú)謂的資源占用
- 如何在有消息到來(lái)時(shí)立即被喚醒隙券。
引入Runloop機(jī)制的目的是利用RunLoop機(jī)制的特點(diǎn)實(shí)現(xiàn)整體省電的效果,并且讓系統(tǒng)和應(yīng)用可以流暢的運(yùn)行痹仙,提高響應(yīng)速度是尔,達(dá)到極致的用戶體驗(yàn)。
本質(zhì)上RunLoop是什么
進(jìn)程是一家工廠开仰,線程是一個(gè)流水線拟枚,Run Loop就是流水線上的主管薪铜;當(dāng)工廠接到商家的訂單分配給這個(gè)流水線時(shí),Run Loop就啟動(dòng)這個(gè)流水線恩溅,讓流水線動(dòng)起來(lái)隔箍,生產(chǎn)產(chǎn)品;當(dāng)產(chǎn)品生產(chǎn)完畢時(shí)脚乡,Run Loop就會(huì)暫時(shí)停下流水線蜒滩,節(jié)約資源。
RunLoop管理流水線奶稠,流水線才不會(huì)因?yàn)闊o(wú)所事事被工廠銷(xiāo)毀俯艰;而不需要流水線時(shí),就會(huì)辭退RunLoop這個(gè)主管锌订,即退出線程竹握,把所有資源釋放。
RunLoop實(shí)質(zhì)上是一個(gè)對(duì)象辆飘,這個(gè)對(duì)象管理了其需要處理的的事件和消息啦辐,并提供了入口函數(shù)來(lái)執(zhí)行Event Loop 的邏輯。線程執(zhí)行這個(gè)函數(shù)后蜈项,會(huì)一直處于這個(gè)函數(shù)內(nèi)部:接受消息->等待->執(zhí)行 的循環(huán)中芹关,直到這個(gè)循環(huán)結(jié)束。
iOS紧卒、OSX系統(tǒng)提供了兩個(gè)RunLoop系統(tǒng):
- Foundation: NSRunLoop 是基于CFRunLoopRef的封裝侥衬,提供了面向?qū)ο蟮腁PI,這些API不是線程安全的
- Core Foundation: CFRunLoopRef 核心部分常侦,代碼開(kāi)源浇冰,C 語(yǔ)言編寫(xiě),跨平臺(tái)聋亡。所有API都是線程安全的
CFRunLoopRef源代碼: http://opensource.apple.com/tarballs/CF/
RunLoop的特性
- 主線程的RunLoop在應(yīng)用啟動(dòng)的時(shí)候就會(huì)自動(dòng)創(chuàng)建
- 其他線程則需要在該線程下自己?jiǎn)?dòng)
- 不能直接創(chuàng)建RunLoop
- RunLoop并不是線程安全的肘习,所以需要避免在其他線程上調(diào)用當(dāng)前線程的RunLoop
- RunLoop負(fù)責(zé)管理autorelease pools
- RunLoop負(fù)責(zé)處理消息事件,即輸入源事件和計(jì)時(shí)器事件
RunLoop與線程的關(guān)系
iOS開(kāi)發(fā)中有兩個(gè)線程對(duì)象:pthread_t和NSThread坡倔。過(guò)去NSThread只是pthread_t的封裝漂佩,現(xiàn)在它們都是直接包裝自最底層的mach thread。
pthread_t同NSThread是一一對(duì)應(yīng)的罪塔⊥恫酰可以通過(guò) pthread_main_np() 或 [NSThread mainThread]獲取主線程。也可以通過(guò) pthread_self() 或 [NSThread currentThread] 獲取當(dāng)前線程征堪。CFRunLoop是基于pthread來(lái)管理的瘩缆,NSRunLoop是基于NSThread管理的。
蘋(píng)果不允許直接創(chuàng)建RunLoop佃蚜,但是提供了兩個(gè)獲取RunLoop的函數(shù)庸娱。CFRunLoopGetMain()和CFRunLoopGetCurrent()着绊。
/// 全局的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)線程銷(xiāo)毀時(shí),順便也銷(xiāo)毀其對(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之間是一一對(duì)應(yīng)的枚粘,其關(guān)系是保存在一個(gè)全局的Dictionary里。線程剛創(chuàng)建時(shí)并沒(méi)有RunLoop飘蚯,如果不主動(dòng)獲取,一直不會(huì)有福也。RunLoop的創(chuàng)建是發(fā)生在第一次獲取時(shí)
RunLoop的內(nèi)部組件
一個(gè)RunLoop可以包含多個(gè)Mode局骤,每個(gè)Mode有包含多個(gè)Source、Timer暴凑、Observer峦甩。每次調(diào)用RunLoop主入口函數(shù)時(shí),只能指定一個(gè)Mode现喳,這個(gè)Mode被稱(chēng)作CurrentMode凯傲。如果需要切換Mode,需要退出本次循環(huán)嗦篱,再重新指定一個(gè)Mode進(jìn)入冰单。
Source、Timer灸促、Observer 被統(tǒng)稱(chēng)為 mode item诫欠,一個(gè) item 可以被同時(shí)加入多個(gè) mode。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的浴栽。如果一個(gè) mode 中一個(gè) item 都沒(méi)有荒叼,則 RunLoop 會(huì)直接退出,不進(jìn)入循環(huán)典鸡。
Source
事件源被廓,在CF中的類(lèi)型是 CFRunLoopSourceRef
事件源是事件產(chǎn)生的地方,有兩個(gè)類(lèi)型:Source0 和 Source1萝玷。
- Source0 只包含了一個(gè)回調(diào)函數(shù)嫁乘,這個(gè)回調(diào)函數(shù)是一個(gè)函數(shù)指針英遭,它不能主動(dòng)觸發(fā)事件。在使用時(shí)亦渗,需要先調(diào)用CFRunLoopSourceSignal(source)挖诸,將這個(gè)Source標(biāo)記為待處理,然后手動(dòng)調(diào)用法精,然后調(diào)用 CFRunLoopWakeUp(runloop) 多律,喚醒RunLoop。
- Source1 包含了一個(gè)Mach_port 和一個(gè)回調(diào)函數(shù)。主要用于通過(guò)內(nèi)核和其他線程相互發(fā)送消息。這種Source能主動(dòng)喚醒RunLoop的線程湖蜕。
Timer
計(jì)時(shí)器划乖,在CF中的類(lèi)型是 CFRunLoopTimerRef
CF計(jì)時(shí)器同NSTimer是toll-free bridged 的,可以互相轉(zhuǎn)換芭届。
包含了一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào)函數(shù)(IMP)。當(dāng)它加入到RunLoop時(shí),RunLoop會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn)丰涉,當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒執(zhí)行timer中的函數(shù)指針斯碌。
Observer
觀察者一死,在CF中的類(lèi)型是CFRunLoopObserverRef
用于監(jiān)聽(tīng)RunLoop的狀態(tài)變化
包含了一個(gè)回調(diào)函數(shù),當(dāng)RunLoop的狀態(tài)發(fā)生變化的時(shí)候傻唾,觀察者可以通過(guò)回調(diào)接收到這個(gè)變化投慈。
有以下?tīng)顟B(tài):
- kCFRunLoopEntry 即將進(jìn)入Loop
- KCFRunLoopBeforeTimers 即將處理 Timer
- KCFRunLoopBeforeSources 即將處理 Source
- KCFRunLoopBeforeWaiting 即將進(jìn)入休眠
- KCFRunLoopAfterWaiting 從休眠中喚醒
- KCFRunLoopExit 退出Loop
Mode
定義
RunLoop Mode就是流水線上支持生產(chǎn)的產(chǎn)品類(lèi)型,流水線在一個(gè)時(shí)刻只能在一種模式下運(yùn)行冠骄,生產(chǎn)某一類(lèi)型的產(chǎn)品伪煤。mode item就是訂單。mode主要是為了分隔開(kāi)不同組的Source凛辣、Timer抱既、Observer,讓其互不影響蟀给。
結(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<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
};
Mode 類(lèi)型
- 1蝙砌、Default:NSDefaultRunLoopMode,默認(rèn)模式跋理,在Run Loop沒(méi)有指定Mode的時(shí)候择克,默認(rèn)就跑在Default Mode下
- 2、Event tracking:UITrackingRunLoopMode前普,拖動(dòng)事件
- 3肚邢、Connection:NSConnectionReplyMode,用來(lái)監(jiān)聽(tīng)處理網(wǎng)絡(luò)請(qǐng)求NSConnection的事件
- 4、Modal:NSModalPanelRunLoopMode骡湖,OS X的Modal面板事件
- 5贱纠、 UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用响蕴。
- 6谆焊、 GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode。
- Common mode:NSRunLoopCommonModes浦夷,是一個(gè)模式集合辖试,當(dāng)綁定一個(gè)事件源到這個(gè)模式集合的時(shí)候就相當(dāng)于綁定到了集合內(nèi)的每一個(gè)模式。
Common Mode
將ModeName添加到RunLoop結(jié)構(gòu)體中的commonModes集合中劈狐, 這個(gè)mode就具有Common屬性罐孝,成為了CommonMode。當(dāng)RunLoop發(fā)生變化或者發(fā)生事件的時(shí)候肥缔,會(huì)將commonModeItems中的Source莲兢、Timer、Observer同步到具有Common屬性的Mode中续膳。
主線程有兩個(gè)預(yù)置Mode改艇。kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個(gè)Mode都已經(jīng)具有Common屬性姑宽。timer加入到DefaultMode中遣耍,默認(rèn)情況會(huì)被調(diào)用,但是當(dāng)ScrollView滾動(dòng)的時(shí)候炮车,RunLoop會(huì)將Mode切換為UITrackingRunLoopMode,這是因?yàn)闉榱烁玫挠脩趔w驗(yàn)酣溃,在主線程中Event tracking模式的優(yōu)先級(jí)最高瘦穆。這時(shí)timer不會(huì)被調(diào)用。如果需要這個(gè)timer在兩個(gè)Mode中都能得到調(diào)用赊豌,可以將timer分別加入這兩個(gè)Mode中扛或。也可以將timer加入到commonModelItems中,這樣會(huì)被所有具有Common屬性的Mode調(diào)用碘饼。
解決方法:
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerHandler:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//或
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[timer fire];
特定時(shí)間執(zhí)行mode
RunLoop可以通過(guò)[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]來(lái)指定在一段時(shí)間內(nèi)的運(yùn)行模式熙兔。如果不指定的話,RunLoop默認(rèn)會(huì)運(yùn)行在Default下(不斷重復(fù)調(diào)用runMode:NSDefaultRunLoopMode beforDate:)
RunLoop 實(shí)現(xiàn)邏輯分析
RunLoop核心代碼
// 用DefaultMode啟動(dòng)
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, 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钠绍。
// Observer會(huì)創(chuàng)建AutoReleasePool _objc_autoreleasePoolPush()
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
// 進(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è)事件喚醒娩脾。
// 基于 port 的Source 的事件
// Timer 的執(zhí)行時(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 即將退出覆旱。
// observer 釋放AutoReleasePool _objc_autoreleasePoolPop()
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
實(shí)際上 RunLoop 其內(nèi)部是一個(gè) do-while 循環(huán)蘸朋。RunLoop的核心就是一個(gè)MachMessage的調(diào)用過(guò)程,RunLoop調(diào)用這個(gè)函數(shù)去接收消息扣唱,如果沒(méi)有別人發(fā)送port消息過(guò)來(lái)藕坯,RunLoop會(huì)進(jìn)入休眠狀態(tài)。內(nèi)核會(huì)將線程置于等待狀態(tài)噪沙,這個(gè)時(shí)候whild循環(huán)是停止的炼彪,相比于一直運(yùn)行的while循環(huán),會(huì)很節(jié)省CPU資源正歼,進(jìn)而達(dá)到省電的目的辐马。
因?yàn)镾ource1注冊(cè)了MachPort端口,當(dāng)有Source1事件進(jìn)來(lái)的時(shí)候會(huì)通過(guò)這個(gè)port端口喚醒RunLoop繼續(xù)執(zhí)行局义,當(dāng)計(jì)時(shí)器到了時(shí)候也會(huì)喚醒RunLoop喜爷,RunLoop喚醒后會(huì)先通過(guò)Observer 當(dāng)前已經(jīng)處于喚醒狀態(tài),之后會(huì)先執(zhí)行Source1事件萄唇,執(zhí)行完成后再執(zhí)行timer檩帐、Source0。執(zhí)行完所有的Source0事件后穷绵,如果有Source1事件則執(zhí)行Source1轿塔,如果沒(méi)有則通知Observe 進(jìn)入到休眠狀態(tài)。完成一個(gè)循環(huán)。
RunLoop 底層機(jī)制分析
操作系統(tǒng)架構(gòu)
iOS/OSX操作系統(tǒng)核心是Darwin勾缭,Darwin包括了硬件層揍障、XNU內(nèi)核和Darwin庫(kù)等組成部分。
XNU內(nèi)核由IOKit俩由、BSD毒嫡、Mach三部分組成。
- IOKit 主要負(fù)責(zé)IO提供硬件通訊功能
- BSD幻梯,是一個(gè)類(lèi)Unix系統(tǒng)兜畸,負(fù)責(zé)進(jìn)程管理、文件系統(tǒng)碘梢、網(wǎng)絡(luò)等功能咬摇。
- Mach是微內(nèi)核,提供處理器調(diào)度煞躬、IPC等基礎(chǔ)服務(wù)肛鹏。
Mach通訊
RunLoop的底層實(shí)現(xiàn)基于Mach內(nèi)核通訊實(shí)現(xiàn)的。在Mach中恩沛,所有的組件都是一個(gè)對(duì)象在扰。進(jìn)程、線程雷客、虛擬內(nèi)存都是對(duì)象芒珠。Mach對(duì)象之間不能直接調(diào)用,對(duì)象間的通訊是通過(guò)消息機(jī)制實(shí)現(xiàn)的搅裙。
Mach 的 IPC 的核心就是消息(message)在兩個(gè)端口(port)之間傳遞皱卓。
消息實(shí)質(zhì)上是一個(gè)二進(jìn)制數(shù)據(jù)包,在頭部定義了當(dāng)前端口和目標(biāo)端口部逮。
消息的發(fā)送接收通過(guò)mach_msg()函數(shù)實(shí)現(xiàn)好爬,mach_msg()函數(shù)內(nèi)部會(huì)通過(guò)調(diào)用mach_msg_trap()函數(shù)將現(xiàn)在的用戶態(tài)切換到內(nèi)核態(tài)。進(jìn)入到內(nèi)核態(tài)后甥啄,會(huì)調(diào)用Mach內(nèi)核的mach_msg()函數(shù)完成消息傳遞工作,注意此mach_msg和第一個(gè)mach_msg雖然函數(shù)名相同炬搭,但是實(shí)現(xiàn)和意義是完全不同的蜈漓。
RunLoop的核心機(jī)制就是通過(guò)調(diào)用 mach_msg 等待接受 mach_port 的消息。線程進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒宫盔。事件通過(guò)mach_port 發(fā)送 mach_msg 喚醒RunLoop融虽。
總結(jié)
RunLoop是一個(gè)do-while 循環(huán),又不是一個(gè)do-while 循環(huán)灼芭。他的工作模式是一個(gè)循環(huán)有额,但是他基于mach_port和mach_msg的 休眠\(yùn)喚醒 機(jī)制確保了他可以在無(wú)任務(wù)的時(shí)候休眠,有任務(wù)的時(shí)候及時(shí)喚醒,相比于一個(gè)普通循環(huán)巍佑,不會(huì)空轉(zhuǎn)茴迁,不會(huì)浪費(fèi)系統(tǒng)資源。RunLoop又通過(guò)不同的工作mode隔離了不同的事件源萤衰,使他們的工作互不影響堕义。這才是RunLoop實(shí)現(xiàn)省電,流暢脆栋,響應(yīng)速度快倦卖,用戶體驗(yàn)好的根本原因;進(jìn)而基于RunLoop的組件如計(jì)時(shí)器椿争、GCD怕膛、界面更新、自動(dòng)釋放池能高效運(yùn)轉(zhuǎn)的根本原因秦踪。
面試考察點(diǎn)
- 為什么引入Runloop機(jī)制褐捻,有什么作用或者好處?
引入Runloop機(jī)制的目的是利用RunLoop機(jī)制的特點(diǎn)實(shí)現(xiàn)整體省電的效果洋侨,并且讓系統(tǒng)和應(yīng)用可以流暢的運(yùn)行舍扰,提高響應(yīng)速度,達(dá)到極致的用戶體驗(yàn)希坚。
- 為什么省電边苹?
主要有兩點(diǎn):一、因?yàn)椴蛔鋈魏尾僮鞯臅r(shí)候主線程Runloop會(huì)處于退出狀態(tài)裁僧,不會(huì)執(zhí)行任何空轉(zhuǎn)邏輯个束,不執(zhí)行代碼自然不消耗CPU資源,自然省電聊疲。二茬底、Runloop提供一種班車(chē)機(jī)制,限制如頁(yè)面刷新等任務(wù)的執(zhí)行頻率获洲,一次Runloop只執(zhí)行一次阱表,防止多次重復(fù)執(zhí)行代碼帶來(lái)的性能損耗。
- 為什么可以流程運(yùn)行贡珊?
一個(gè)app流暢與否的決定性因素是主線程的阻塞率最爬,在iOS系統(tǒng)中runloop每秒執(zhí)行60次,理論上主線程runloop達(dá)到55幀以上的刷新頻率用戶就感覺(jué)不到卡頓门岔。
Mode機(jī)制爱致,同一時(shí)間只執(zhí)行一個(gè)Mode內(nèi)的Source或者Timer,比如拖動(dòng)的時(shí)候只指定拖動(dòng)Mode寒随,其他Mode 如Default Mode中的源不會(huì)被執(zhí)行糠悯,確保了高速滑動(dòng)的時(shí)候不會(huì)有其他邏輯阻礙主線程刷新帮坚。
Runloop做的是管理視圖刷新頻率,防止重復(fù)運(yùn)算互艾。由于視圖更新必須在主線程试和,視圖的重布局和重繪都會(huì)占用主線程的執(zhí)行時(shí)間,一次Runloop循環(huán)只執(zhí)行一次可以最大限度的防止重復(fù)運(yùn)算導(dǎo)致的計(jì)算浪費(fèi)忘朝。
管理核心動(dòng)畫(huà)灰署。核心動(dòng)畫(huà)有三個(gè)樹(shù),其中render tree 是私有的局嘁,應(yīng)用開(kāi)發(fā)無(wú)法訪問(wèn)到溉箕。render tree在專(zhuān)用的render server 進(jìn)程中執(zhí)行,是真正用來(lái)渲染動(dòng)畫(huà)的地方悦昵,線程優(yōu)先級(jí)高于主線程肴茄。所以即使app主線程阻塞,也不會(huì)影響到動(dòng)畫(huà)的繪制工作但指。既節(jié)省了主線程的計(jì)算資源寡痰,又使動(dòng)畫(huà)可以流暢的執(zhí)行。
支持異步方法調(diào)用棋凳,將耗時(shí)操作分發(fā)到子線程中進(jìn)行拦坠。RunLoop是performSelector的基礎(chǔ)設(shè)施。我們使用 performSelector:onThread: 或者 performSelecter:afterDelay: 時(shí)剩岳,實(shí)際上系統(tǒng)會(huì)創(chuàng)建一個(gè)Timer并添加到當(dāng)前線程的RunLoop中贞滨。
還有其他的點(diǎn),這里不展開(kāi)拍棕,詳情可閱讀下文應(yīng)用實(shí)踐晓铆。
當(dāng)然Runloop不是萬(wàn)能的,如果代碼質(zhì)量差绰播,在一次Runloop循環(huán)中執(zhí)行的時(shí)間過(guò)久一樣會(huì)導(dǎo)致卡頓骄噪,所以解決卡頓問(wèn)題也是程序員能力的體現(xiàn)。
- 如何提高響應(yīng)速度蠢箩?
當(dāng)發(fā)生系統(tǒng)事件時(shí)链蕊,如觸碰事件,系統(tǒng)通過(guò)Mach Port 發(fā)送 Mach消息主動(dòng)喚醒Runloop谬泌。Mach是搶占式操作系統(tǒng)內(nèi)核示弓,Mach系統(tǒng)IPC機(jī)制就是依靠消息機(jī)制實(shí)現(xiàn)的,所以效率非常高呵萨。
iOS系統(tǒng)中RunLoop的應(yīng)用實(shí)踐在下一篇文章中闡述:深入淺出 RunLoop (2) — 應(yīng)用實(shí)踐