深入淺出 RunLoop (1) — 核心機(jī)制

前言

這篇文章是兩年前在公司內(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)典鸡。

Runloop mode

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
RunLoop狀態(tài)轉(zhuǎn)換

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的內(nèi)部運(yù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í)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跨跨,隨后出現(xiàn)的幾起案子潮峦,更是在濱河造成了極大的恐慌囱皿,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忱嘹,死亡現(xiàn)場(chǎng)離奇詭異嘱腥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拘悦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)齿兔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人础米,你說(shuō)我怎么就攤上這事分苇。” “怎么了屁桑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵医寿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蘑斧,道長(zhǎng)靖秩,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任竖瘾,我火速辦了婚禮沟突,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捕传。我一直安慰自己惠拭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布乐横。 她就那樣靜靜地躺著求橄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪葡公。 梳的紋絲不亂的頭發(fā)上罐农,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音催什,去河邊找鬼涵亏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蒲凶,可吹牛的內(nèi)容都是我干的气筋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼旋圆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宠默!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起灵巧,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搀矫,失蹤者是張志新(化名)和其女友劉穎抹沪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體瓤球,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡融欧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卦羡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片噪馏。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绿饵,靈堂內(nèi)的尸體忽然破棺而出欠肾,到底是詐尸還是另有隱情,我是刑警寧澤蝴罪,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布董济,位于F島的核電站,受9級(jí)特大地震影響要门,放射性物質(zhì)發(fā)生泄漏虏肾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一欢搜、第九天 我趴在偏房一處隱蔽的房頂上張望封豪。 院中可真熱鬧,春花似錦炒瘟、人聲如沸吹埠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缘琅。三九已至,卻和暖如春廓推,著一層夾襖步出監(jiān)牢的瞬間刷袍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工樊展, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呻纹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓专缠,卻偏偏與公主長(zhǎng)得像雷酪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涝婉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,438評(píng)論 0 13
  • 前言 RunLoop是iOS和OSX開(kāi)發(fā)中非掣缌Γ基礎(chǔ)的一個(gè)概念,這篇文章將從CFRunLoop的源碼入手墩弯,介紹Run...
    暮年古稀ZC閱讀 2,242評(píng)論 1 19
  • RunLoop 的概念 一般來(lái)講省骂,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù)蟀淮,執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制钞澳,讓線...
    Mirsiter_魏閱讀 618評(píng)論 0 2
  • 轉(zhuǎn)自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飄金閱讀 983評(píng)論 0 4
  • 現(xiàn)在h5特別火,項(xiàng)目越來(lái)越多的使用web頁(yè)面,有些需要修改,感覺(jué)學(xué)點(diǎn)JavaScript還是很有必要的。在學(xué)js之...
    Kan__閱讀 8,713評(píng)論 4 7