RunLoop詳解

寫在前面

本文僅是自己學(xué)習(xí)RunLoop的一個(gè)記錄,參考了ibireme大神的 深入理解RunLoop,加入了自己的理解,時(shí)間原因還不夠細(xì)致,后期慢慢豐富。

再次感謝ibireme大神

1. RunLoop的概念

通常來(lái)講,一個(gè)線程在執(zhí)行完任務(wù)后,會(huì)直接退出。 但在我們?nèi)粘5腁pp使用中,即使什么都不做,App也不會(huì)退出。這其中的原理,便是在線程中構(gòu)建一個(gè)消息循環(huán),使得這個(gè)線程中一直有任務(wù)執(zhí)行。在iOS和OSX中,這個(gè)消息循環(huán)的機(jī)制便被稱為RunLoop

RunLoop 可以保持程序的持續(xù)運(yùn)行,它負(fù)責(zé)處理事件和消息,可以讓線程在沒有消息的時(shí)候休眠,在有消息的時(shí)候才喚醒做事, 以節(jié)省CPU資源。

偽代碼類似這樣

void runloop() {
   int retVal = 0;
   do {
       int message = sleep_and_wait();
       retVal = process_message(message);
   } while (0 == retVal);
}

OSX/iOS系統(tǒng)中,提供了2套API來(lái)訪問和使用RunLoop

CoreFoundation框架中的CFRunLoopRef對(duì)象,它提供了純C函數(shù)的API,是線程安全的。蘋果開源了CFRunLoopRef

Foundation框架中的NSRunLoop對(duì)象,它是基于 CFRunLoopRef的封裝,但是這些 API 不是線程安全的。

2. RunLoop與線程

首先,iOS 開發(fā)中能遇到兩個(gè)線程對(duì)象: pthread_tNSThread。過去蘋果有份文檔標(biāo)明了NSThread只是 pthread_t的封裝,但那份文檔已經(jīng)失效了,現(xiàn)在它們也有可能都是直接包裝自最底層的mach thread。蘋果并沒有提供這兩個(gè)對(duì)象相互轉(zhuǎn)換的接口,但不管怎么樣,可以肯定的是pthread_tNSThread是一一對(duì)應(yīng)的。比如,你可以通過 pthread_main_thread_np()[NSThread mainThread] 來(lái)獲取主線程;也可以通過 pthread_self()[NSThread currentThread] 來(lái)獲取當(dāng)前線程。CFRunLoop 是基于pthread來(lái)管理的。

蘋果不允許直接創(chuàng)建 RunLoop,它只提供了兩個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain()CFRunLoopGetCurrent()。 這兩個(gè)函數(shù)內(nèi)部的邏輯大概是下面這樣:

// 保存RunLoop的全局字典,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
 
    if (!__CFRunLoops) {
        // 第一次進(jìn)入時(shí),初始化全局Dic,并先為主線程創(chuàng)建一個(gè) RunLoop。
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
    }
    // 從__CFRunLoops字典中取出當(dāng)前傳入的key對(duì)應(yīng)的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
 
    if (!loop) {
        // 沒有取到就創(chuàng)建一個(gè) (所以子線程不獲取就不創(chuàng)建RunLoop)
        loop = __CFRunLoopCreate(t);
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), loop);
    }
     // 注冊(cè)一個(gè)回調(diào),當(dāng)線程銷毀時(shí),順便也銷毀其對(duì)應(yīng)的 RunLoop。
    _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
 
    return loop;
}

從上面的代碼可以看出

  • 線程RunLoop 之間是一一對(duì)應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。
  • 子線程剛創(chuàng)建時(shí)并沒有RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有。
    *RunLoop 會(huì)在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束時(shí)銷毀。
  • 主線程RunLoop已經(jīng)自動(dòng)獲取(創(chuàng)建),子線程默認(rèn)沒有開啟RunLoop

3. RunLoop 的結(jié)構(gòu)

CoreFoundation框架中有關(guān)于RunLoop的5個(gè)類

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopObserverRef
CFRunLoopTimerRef

// 對(duì)外暴露了,聲明在CFRunLoop.c文件中
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

// 沒有對(duì)外暴露,聲明在CFRunLoop.c文件中
typedef struct __CFRunLoopMode *CFRunLoopModeRef;

3.1 CFRunLoopRef 與 CFRunLoopModeRef
// 結(jié)構(gòu)體定義
struct __CFRunLoop {
    pthread_t _pthread;  // runloop和線程是一一對(duì)應(yīng)關(guān)系,每個(gè)runloop內(nèi)部會(huì)保留一個(gè)對(duì)應(yīng)的線程
    CFMutableSetRef _commonModes;  //標(biāo)記為common的mode的集合
    CFMutableSetRef _commonModeItems;  //commonMode的item集合
    CFRunLoopModeRef _currentMode;  // 當(dāng)前的模式
    CFMutableSetRef _modes; // CFRunLoopModeRef類型的集合,相對(duì)NSArray有序,Set為無(wú)序集合
};

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

每個(gè)RunLoop中包含了多個(gè)ModeMode里面又包含了多個(gè)Source/Observer/Timer,其中source分為source0source1, 多個(gè)mode里,有且僅有一個(gè)為currentMode。如果要切換Mode,只能退出當(dāng)前 Loop,再重新指定一個(gè)Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。

runloop內(nèi)部modes.png

這里有個(gè)Common的概念。我們可以將一個(gè)Mode標(biāo)記為Common屬性,當(dāng)Mode被標(biāo)記為Common時(shí),會(huì)將該Mode添加到CommonModes里,而當(dāng)Timer/Source/Observer被添加到CommonModes里面的時(shí)候,系統(tǒng)會(huì)遍歷所有的CommonModes成員,該Timer/Source/Observer會(huì)被添加到所有CommonModes成員中,也就是該Timer/Source/Observer會(huì)被添加到CommonModeItems集合里

主線程里默認(rèn)UITrackingRunLoopMode,kCFRunLoopDefaultMode被標(biāo)記了Common,子線程中默認(rèn)只有kCFRunLoopDefaultMode被標(biāo)記。

我們可以通過CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode)將一個(gè)Mode標(biāo)記為Common

image.png
3.2 CFRunLoopSourceRef

CFRunLoopSourceRef是事件產(chǎn)生的地方。Source有兩個(gè)版本:Source0Source1

  • Source0 只包含了一個(gè)回調(diào) ,它并不能主動(dòng)觸發(fā)事件。使用時(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) ,被用于通過內(nèi)核和其他線程相互發(fā)送消息。這種Source 能主動(dòng)喚醒RunLoop 的線程.
struct __CFRunLoopSource {
    CFRuntimeBase _base; 
    uint32_t _bits;  //用于標(biāo)記Signaled狀態(tài),source0只有在被標(biāo)記為Signaled狀態(tài),才會(huì)被觸發(fā)
    pthread_mutex_t _lock;
    CFIndex _order;         /* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;     // source0
        CFRunLoopSourceContext1 version1;    // source1
    } _context;
};

typedef struct {
    CFIndex version;
    void *  info;
    void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;

typedef struct {
    CFIndex version;
    void *  info;
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
 
} CFRunLoopSourceContext1;
 
3.3 CFRunLoopTimerRef

CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器,它和NSTimertoll-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)。

從下面的結(jié)構(gòu)體中可以看到,結(jié)構(gòu)體內(nèi)部有一個(gè)runloop成員,它是timer所在的RunLoop,一個(gè)timer僅能夠添加到一個(gè)RunLoop中去,一旦timer被添加到RunLoop中,再有其他的RunLoop添加該timer,其實(shí)是無(wú)效的。

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits; //標(biāo)記fire狀態(tài)
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop; //添加該timer的runloop
    CFMutableSetRef _rlModes; //存放所有 包含該timer的 mode的 modeName,意味著一個(gè)timer可能會(huì)在多個(gè)mode中存在
    CFAbsoluteTime _nextFireDate; // 下一次觸發(fā)時(shí)間
    CFTimeInterval _interval;    //執(zhí)行的時(shí)間間隔
    CFTimeInterval _tolerance;   //時(shí)間偏差
    uint64_t _fireTSR;      // 觸發(fā)時(shí)間
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    // 回調(diào)
    CFRunLoopTimerContext _context;  // 回調(diào)內(nèi)容
};
3.4 CFRunLoopObserverRef

CFRunLoopObserverRef是觀察者,和CFRunLoopTimerRef一樣,它的結(jié)構(gòu)體內(nèi)部包含著一個(gè)_runloop成員, 表明每個(gè)Observer同時(shí)只能監(jiān)聽一個(gè)RunLoop, 每個(gè) Observer 都包含了一個(gè)回調(diào),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過回調(diào)接受到這個(gè)變化。可以觀測(cè)的時(shí)間點(diǎn)有以下幾個(gè):

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  // 線程鎖
    CFRunLoopRef _runLoop; // 所在的runloop
    CFIndex _rlCount; // Observer監(jiān)控的runloop數(shù)量 邏輯主要在__CFRunLoopObserverSchedule和__CFRunLoopObserverCancel
    CFOptionFlags _activities;      // RunLoop的幾個(gè)狀態(tài) - immutable
    CFIndex _order;         /* immutable */
    CFRunLoopObserverCallBack _callout; // 回調(diào)
    CFRunLoopObserverContext _context;  // 回調(diào)的參數(shù)
};

上面的Source/Timer/Observer 被統(tǒng)稱為mode item,一個(gè)item可以被同時(shí)加入多個(gè)mode。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的。如果一個(gè)mode中一個(gè)item 都沒有,則 RunLoop會(huì)直接退出,不進(jìn)入循環(huán)。

4. RunLoop 對(duì)外的接口

CFRunLoop對(duì)外暴露的管理 Mode接口只有下面2個(gè):

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);

Mode 暴露的管理 mode item 的接口有下面幾個(gè):

CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

你只能通過mode name來(lái)操作內(nèi)部的 mode,當(dāng)你傳入一個(gè)新的mode nameRunLoop內(nèi)部沒有對(duì)應(yīng) mode 時(shí),RunLoop會(huì)自動(dòng)幫你創(chuàng)建對(duì)應(yīng)的 CFRunLoopModeRef。對(duì)于一個(gè)RunLoop來(lái)說(shuō),其內(nèi)部的 mode 只能增加不能刪除。

蘋果公開提供的 Mode 有兩個(gè):kCFRunLoopDefaultMode (NSDefaultRunLoopMode)UITrackingRunLoopMode,你可以用這兩個(gè) Mode Name 來(lái)操作其對(duì)應(yīng)的 Mode

5. RunLoop 內(nèi)部流程圖

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知Observers, 進(jìn)入RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // RunLoop里面具體要做的事情, 主要是一個(gè)循環(huán)
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知Observers, 退出 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

  
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
 
    // 當(dāng)前是否為主線程
    bool cond1 = pthread_main_np();
    // _CFGetTSD(__CFTSDKeyIsInGCDMainQ) 從預(yù)先分配的插槽中獲取主線程的一些特定數(shù)據(jù)
    // CF源碼中沒有搜索到_CFSetTSD(__CFTSDKeyIsInGCDMainQ)的調(diào)用,推測(cè)但不完全確定_CFGetTSD(__CFTSDKeyIsInGCDMainQ) 為空
    bool cond2 = 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ);
    // 當(dāng)前是否為主線程
    bool cond3 = CFRunLoopGetMain() == rl;
    // 當(dāng)前的mode包含在commonModes里面
    bool cond4 = CFSetContainsValue(rl->_commonModes, rlm->_name);
 
    // 當(dāng)前為主線程且mode被標(biāo)記為common,dispatchPort才會(huì)被賦值
    if (cond1 && cond2 && cond3 && cond4) {
        dispatchPort = _dispatch_get_main_queue_port_4CF();
    }

 
    Boolean didDispatchPortLastTime = true;

    int32_t retVal = 0;
    do {

        __CFRunLoopUnsetIgnoreWakeUps(rl);

        // 通知observers, 即將處理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知observers, 即將處理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        // 處理source0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        // timeout 傳入0 表示立即返回 傳入TIMEOUT_INFINITY 表示等待到消息再返回
        
        // 當(dāng)前為主線程且mode被標(biāo)記為common,且非第一次循環(huán)
        if (dispatchPort && !didDispatchPortLastTime) {
            // 主線程里有消息處理,直接跳轉(zhuǎn)到handle_msg
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }
        didDispatchPortLastTime = false;
 
        // 通知observers, 即將進(jìn)入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);

        // 線程進(jìn)入休眠,等待喚醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

        __CFRunLoopUnsetSleeping(rl);
        // 通知observers, 已經(jīng)結(jié)束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        // 處理消息
        handle_msg:;

        if (msg_is_timer) {
            // 處理timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        } else if (msg_is_dispatch) {
            // 處理GCD  如果有dispatch到main_queue的block,執(zhí)行block
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            // 處理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
 
        }
 
        // 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        

          // 設(shè)置返回值
       if (sourceHandledThisLoop && stopAfterHandle) {
          //stopAfterHandle 是傳入的標(biāo)記,如果處理完事件就返回
          retVal = kCFRunLoopRunHandledSource;
       } else if (timeout_context->termTSR < mach_absolute_time()) {
           //設(shè)置了超時(shí)時(shí)間,超時(shí)返回
          retVal = kCFRunLoopRunTimedOut;
       } else if (__CFRunLoopIsStopped(rl)) {
            // 被外部調(diào)用者強(qiáng)制停止了
          __CFRunLoopUnsetStopped(rl);
         retVal = kCFRunLoopRunStopped;
       } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
          // 內(nèi)部source,observers,timers 都空了,返回
          retVal = kCFRunLoopRunFinished;
       }
        
    // 如果不進(jìn)入上述的條件,繼續(xù)循環(huán)
    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

流程圖示如下:


RunLoop內(nèi)部流程示意.png

6. RunLoop 的底層實(shí)現(xiàn)

從RunLoop的源碼中可以看到,RunLoop 的核心是基于 mach port 的,其進(jìn)入休眠時(shí)調(diào)用的函數(shù)是 mach_msg()。為了解釋這個(gè)邏輯,下面稍微介紹一下 OSX/iOS 的系統(tǒng)架構(gòu)。

蘋果官方將整個(gè)系統(tǒng)大致劃分為上述4個(gè)層次:
應(yīng)用層包括用戶能接觸到的圖形應(yīng)用,例如 Spotlight、Aqua、SpringBoard 等。
應(yīng)用框架層即開發(fā)人員接觸到的 Cocoa 等框架。
核心框架層包括各種核心框架、OpenGL 等內(nèi)容。
Darwin 即操作系統(tǒng)的核心,包括系統(tǒng)內(nèi)核、驅(qū)動(dòng)、Shell 等內(nèi)容,這一層是開源的,其所有源碼都可以在 opensource.apple.com 里找到。

我們?cè)谏钊肟匆幌?Darwin 這個(gè)核心的架構(gòu):

其中,在硬件層上面的三個(gè)組成部分:Mach、BSD、IOKit (還包括一些上面沒標(biāo)注的內(nèi)容),共同組成了 XNU 內(nèi)核。
XNU 內(nèi)核的內(nèi)環(huán)被稱作 Mach,其作為一個(gè)微內(nèi)核,僅提供了諸如處理器調(diào)度、IPC (進(jìn)程間通信)等非常少量的基礎(chǔ)服務(wù)。
BSD 層可以看作圍繞 Mach 層的一個(gè)外環(huán),其提供了諸如進(jìn)程管理、文件系統(tǒng)和網(wǎng)絡(luò)等功能。
IOKit 層是為設(shè)備驅(qū)動(dòng)提供了一個(gè)面向?qū)ο?C++)的一個(gè)框架。

Mach 本身提供的 API 非常有限,而且蘋果也不鼓勵(lì)使用 Mach 的 API,但是這些API非常基礎(chǔ),如果沒有這些API的話,其他任何工作都無(wú)法實(shí)施。在 Mach 中,所有的東西都是通過自己的對(duì)象實(shí)現(xiàn)的,進(jìn)程、線程和虛擬內(nèi)存都被稱為”對(duì)象”。和其他架構(gòu)不同, Mach 的對(duì)象間不能直接調(diào)用,只能通過消息傳遞的方式實(shí)現(xiàn)對(duì)象間的通信。”消息”是 Mach 中最基礎(chǔ)的概念,消息在兩個(gè)端口 (port) 之間傳遞,這就是 Mach 的 IPC (進(jìn)程間通信) 的核心。

Mach 的消息定義是在 <mach/message.h> 頭文件的,很簡(jiǎn)單:

typedef struct{
    mach_msg_header_t       header;
    mach_msg_body_t         body;
} mach_msg_base_t;

typedef struct{
    mach_msg_bits_t       msgh_bits;
    mach_msg_size_t       msgh_size;
    mach_port_t           msgh_remote_port;
    mach_port_t           msgh_local_port;
    mach_port_name_t      msgh_voucher_port;
    mach_msg_id_t         msgh_id;
} mach_msg_header_t;

一條 Mach 消息實(shí)際上就是一個(gè)二進(jìn)制數(shù)據(jù)包 (BLOB),其頭部定義了當(dāng)前端口 local_port 和目標(biāo)端口 remote_port,
發(fā)送和接受消息是通過同一個(gè) API 進(jìn)行的,其 option 標(biāo)記了消息傳遞的方向:

/*
 *  Routine:    mach_msg
 *  Purpose:
 *      Send and/or receive a message.  If the message operation
 *      is interrupted, and the user did not request an indication
 *      of that fact, then restart the appropriate parts of the
 *      operation silently (trap version does not restart).
 */
__WATCHOS_PROHIBITED __TVOS_PROHIBITED
extern mach_msg_return_t        mach_msg(
    mach_msg_header_t *msg,
    mach_msg_option_t option,
    mach_msg_size_t send_size,
    mach_msg_size_t rcv_size,
    mach_port_name_t rcv_name,
    mach_msg_timeout_t timeout,
    mach_port_name_t notify);

為了實(shí)現(xiàn)消息的發(fā)送和接收,mach_msg() 函數(shù)實(shí)際上是調(diào)用了一個(gè) Mach 陷阱 (trap),即函數(shù)mach_msg_trap(),陷阱這個(gè)概念在 Mach 中等同于系統(tǒng)調(diào)用。當(dāng)你在用戶態(tài)調(diào)用 mach_msg_trap() 時(shí)會(huì)觸發(fā)陷阱機(jī)制,切換到內(nèi)核態(tài);內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的 mach_msg() 函數(shù)會(huì)完成實(shí)際的工作,如下圖:

這些概念可以參考維基百科: System_callTrap_(computing)

RunLoop 的核心就是一個(gè) mach_msg() ,RunLoop 調(diào)用這個(gè)函數(shù)去接收消息,如果沒有別人發(fā)送 port 消息過來(lái),內(nèi)核會(huì)將線程置于等待狀態(tài)。例如你在模擬器里跑起一個(gè) iOS 的 App,然后在 App 靜止時(shí)點(diǎn)擊暫停,你會(huì)看到主線程調(diào)用棧是停留在 mach_msg_trap() 這個(gè)地方。

關(guān)于具體的如何利用 mach port 發(fā)送信息,可以看看 NSHipster 這一篇文章,或者這里的中文翻譯 。

關(guān)于Mach的歷史可以看看這篇很有趣的文章:Mac OS X 背后的故事(三)Mach 之父 Avie Tevanian

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,629評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評(píng)論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,411評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,641評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,820評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,362評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,604評(píng)論 2 380

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

  • 不得不說(shuō),人的惰性是真可怕啊。從上周六就到寫runLoop的建議開始,星期三告訴自己從星期四開始著手寫這篇博客。然...
    老司機(jī)Wicky閱讀 7,178評(píng)論 20 137
  • 深入理解RunLoop[https://blog.ibireme.com/2015/05/18/runloop/]...
    醉臥欄桿聽雨聲閱讀 482評(píng)論 0 0
  • 此文轉(zhuǎn)載自深入理解RunLoop,為了方便自己閱讀,發(fā)在簡(jiǎn)書上RunLoop 是 iOS 和 OS X 開發(fā)中非常...
    漸行漸遠(yuǎn)ty閱讀 401評(píng)論 0 0
  • 轉(zhuǎn)自 iOS RunLoop 詳解 image.png Runloop 是和線程緊密相關(guān)的基礎(chǔ)組件,是很多多線程有...
    shen888閱讀 203評(píng)論 0 0
  • 一.Runloop介紹 1.什么是Runloop 字面意思運(yùn)行循環(huán),它是一個(gè)對(duì)象,這個(gè)對(duì)象提供一個(gè)入口函數(shù)。程序會(huì)...
    king_jensen閱讀 704評(píng)論 0 0