RunLoop源碼學(xué)習(xí)

通常我們開發(fā)iOS app時(shí)接觸到的是NSRunLoop,而NSRunLoop實(shí)際上是對(duì)蘋果的Core Foundation框架中CFRunLoop的封裝,這次我們直接通過官方文檔和Core Foundation源碼學(xué)習(xí)CFRunLoop。

Core Foundation是純C版本的實(shí)現(xiàn),蘋果已經(jīng)開源了Core Foundation的源碼,相關(guān)鏈接: 官方文檔 源碼下載

CFRunLoop

頭文件中的聲明

typedef struct __CFRunLoop * CFRunLoopRef;

__CFRunLoop的定義(只保留了主要屬性)

struct __CFRunLoop {
    CFMutableSetRef _commonModes;//common mode集合,后邊會(huì)講
    CFMutableSetRef _commonModeItems;//source集合
    CFRunLoopModeRef _currentMode;//當(dāng)前生效的mode
    CFMutableSetRef _modes;//當(dāng)前runloop的所有mode
    struct _block_item *_blocks_head;//block鏈表頭
    struct _block_item *_blocks_tail;//block鏈表尾部
};

可以看到,一個(gè)run loop對(duì)象包含的元素并不多,其中_commonModes、_commonModeItems、_currentMode、_modes都跟mode有關(guān),剩余兩個(gè)屬性是一個(gè)鏈表,用來保存block。

那么mode是什么,接下來看一看mode的定義

CFRunLoopMode

如下是CFRunLoopModeRef的定義:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name;//mode的名字
    Boolean _stopped;//用于退出當(dāng)前mode的方法
    //輸入源 source
    CFMutableSetRef _sources0;//非port相關(guān)事件(如用戶點(diǎn)擊)
    CFMutableSetRef _sources1;//port相關(guān)的事件(系統(tǒng)事件)
    //監(jiān)聽者 observer
    CFMutableArrayRef _observers;
    CFIndex _observerMask;//監(jiān)聽者注冊(cè)的監(jiān)聽事件
    //計(jì)時(shí)器 timer
    CFMutableArrayRef _timers;
};  

一個(gè)mode包含三種類型的對(duì)象,sources(CFRunLoopSource)、timers(CFRunLoopTimer)和observers(CFRunLoopObserver),要接受run loop的回調(diào),必須依賴這三種對(duì)象。他們和run loop以及mode之間的關(guān)系如下:

image.png

每一個(gè)線程對(duì)應(yīng)一個(gè)run loop對(duì)象,每一個(gè)run loop包含多個(gè)mode,每一個(gè)mode包含多個(gè)source、timer以及observer。

每個(gè)source、timer和observer必須添加到一個(gè)或多個(gè)mode中才能生效,但是run loop同時(shí)只能運(yùn)行一個(gè)mode,如果當(dāng)前運(yùn)行的mode不是添加的mode,則不會(huì)生效。

舉個(gè)UIScrollView的例子

主線程的run loop默認(rèn)運(yùn)行的是NSDefaultRunLoopMode,而當(dāng)滑動(dòng)UIScrollView時(shí),主線程的run loop會(huì)切換到UITrackingRunLoopMode。

如果在主線程的NSDefaultRunLoopMode中加入了一個(gè)NSTimer,當(dāng)用戶滑動(dòng)UIScrollView的時(shí)候,主線程會(huì)將run loop切換到UITrackingRunLoopMode,這時(shí)這個(gè)NSTimer就不會(huì)生效。直到用戶停止滑動(dòng)時(shí),主線程將run loop切換回NSDefaultRunLoopMode,此時(shí)計(jì)時(shí)器才會(huì)生效。

系統(tǒng)定義了一些mode,常見的有

  • 默認(rèn)mode:NSDefaultRunLoopMode(Cocoa) / kCFRunLoopDefaultMode(Core Foundation)
  • 事件跟蹤:UITrackingRunLoopMode(Cocoa)
  • Common Modes:NSRunLoopCommonModes(Cocoa) / kCFRunLoopCommonModes(Core Foundation)

當(dāng)我們需要執(zhí)行一些優(yōu)先級(jí)較高的任務(wù)時(shí),也可以自定義mode,限制一些低優(yōu)先級(jí)的事件,保證高優(yōu)先級(jí)任務(wù)的執(zhí)行。

每一個(gè)Mode通過name區(qū)分,Core Foundation沒有對(duì)外暴露run loop mode的接口,使用者只需要關(guān)心mode的name即可,如NSDefaultRunLoopMode、kCFRunLoopDefaultModeUITrackingRunLoopMode,他們都是string類型(可以通過toll-free bridge轉(zhuǎn)換)。

kCFRunLoopCommonModes

__CFRunLoop的定義中,如下兩句定義了common modes相關(guān)的變量:

CFMutableSetRef _commonModes;//common mode集合
CFMutableSetRef _commonModeItems;//source/timer/observer的集合

Common modes不是一個(gè)mode,而是很多mode的集合。被添加到common modes中的mode,會(huì)存儲(chǔ)在_commonModes中,同時(shí)會(huì)將_commonModeItems中的元素添加到這個(gè)mode中,這樣_commonModeItems可以被_commonModes包含的每一個(gè)mode運(yùn)行時(shí)監(jiān)聽到。

從使用者的角度來說,當(dāng)添加了一個(gè)timer到NSRunLoopCommonModes中,無論當(dāng)前run loop運(yùn)行在哪一種mode下,只要這個(gè)mode在common modes集合中,這個(gè)NSTimer就會(huì)生效。

再拿上面UIScrollView舉個(gè)例子

在主線程中,NSDefaultRunLoopModeUITrackingRunLoopMode這兩個(gè)mode被系統(tǒng)加入了common modes中。這意味著,我們?nèi)绻麑?code>NSTimer加入到common modes中,也就是添加到kCFRunLoopCommonModes,此時(shí)無論用戶是否滑動(dòng)UIScrollView,這個(gè)NSTimer都會(huì)生效。

這里有兩種方式與common modes打交道,一個(gè)是添加自定義mode到common modes中,另一個(gè)是添加sources/timers/observers到kCFRunLoopCommonModes,來看看對(duì)應(yīng)的實(shí)現(xiàn)

1. CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    //判斷common modes是否已經(jīng)存在該mode
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
        //獲取common modes set
        CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
        //添加mode進(jìn)common modes set
        CFSetAddValue(rl->_commonModes, modeName);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, modeName};
            //將common mode items中的每一個(gè)item添加到傳入的mode中
            CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
        }
    }
}

當(dāng)調(diào)用CFRunLoopAddCommonMode函數(shù)時(shí),會(huì)將新的mode加入到該runloop的common modes集合中,同時(shí),會(huì)將當(dāng)前common mode items中的所有元素,添加到新的mode中。

如此一來,無論run loop被切換到哪一個(gè)mode,只要這個(gè)mode被加入到kCFRunLoopCommonModes中,就可以響應(yīng)那些被添加到kCFRunLoopCommonModes的sources/timers/observers。

2. CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer

可以在將source/timer/observer三種對(duì)象加入到run loop時(shí)傳入kCFRunLoopCommonModes參數(shù),這樣run loop運(yùn)行在common modes的mode時(shí),這些source/timer/observer就會(huì)生效。這里僅放一個(gè)CFRunLoopAddTimer中與common modes有關(guān)的代碼,其他兩種(source/observer)代碼都是類似的。

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {   
    //判斷是否是將timer加入到kCFRunLoopCommonModes
    if (modeName == kCFRunLoopCommonModes) {
        //獲取common modes集合
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        //如果common modes items為空,則創(chuàng)建一個(gè)common modes items的集合
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //將傳入的timer source加入到common mode items中
        CFSetAddValue(rl->_commonModeItems, rlt);
        //將timer添加到common modes中的每一個(gè)mode中
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        }
    } else {
      //非common mode的操作
    }
}

將一個(gè)timer加入到kCFRunLoopCommonModes中后,會(huì)將這個(gè)timer加入到common mode items里,同時(shí)也會(huì)將這個(gè)timer加入到common modes集合中的每一個(gè)mode中,也就是說,該函數(shù)會(huì)幫你更新所有在common modes集合中的mode,這樣就達(dá)到了無論運(yùn)行在任意一個(gè)common mode時(shí),都可以使這個(gè)timer生效。

CFRunLoopSource/CFRunLoopTimer/CFRunLoopObserver

我們可以通過將source/timer/observer這三種對(duì)象加入到run loop中,當(dāng)有事件發(fā)生時(shí),通過回調(diào)接受通知。在加入到run loop時(shí),必須指定一個(gè)mode。當(dāng)然也可以從一個(gè)run loop中移除上述三種對(duì)象。

CFRunLoopSourceRef

Input source是事件發(fā)生的來源,通常產(chǎn)生異步事件,比如消息到達(dá)網(wǎng)絡(luò)端口或者用戶執(zhí)行的操作。它的定義如下:

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;//toll-free bridge
struct __CFRunLoopSource {
    CFMutableBagRef _runLoops;
    union {
            CFRunLoopSourceContext version0;//source0
        CFRunLoopSourceContext1 version1;//source1
    } _context;
};

CFRunLoopSourceRef中,最主要的是兩個(gè)變量CFRunLoopSourceContextCFRunLoopSourceContext1,它們用union來聲明,意味著有兩種不同的source,source0和source1。

source0

source0是一般響應(yīng)應(yīng)用程序事件,例如按鈕的響應(yīng)事件。當(dāng)需要發(fā)送source0事件時(shí),調(diào)用CFRunLoopSourceSignal函數(shù)將這個(gè)source標(biāo)記為待觸發(fā),但是該函數(shù)不能喚醒runloop,需要再調(diào)用CFRunLoopWakeUp方法喚醒對(duì)應(yīng)的run loop,這個(gè)source0事件才會(huì)被觸發(fā)。Core Foundation中的CFSocket使用的是source0的方式實(shí)現(xiàn)。定義如下:

typedef struct {
    CFIndex version;//0代表是source0
    void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;

它包含三個(gè)回調(diào),

  • schedule:當(dāng)添加source0到run loop時(shí),會(huì)調(diào)用一次schedule回調(diào)方法;
  • perform:當(dāng)觸發(fā)source0時(shí),會(huì)調(diào)用perform回調(diào)方法;
  • cancel:當(dāng)移除或者run loop銷毀時(shí),會(huì)調(diào)用cancel回調(diào)方法。
source1

source1由run loop和內(nèi)核管理,使用mach port進(jìn)行通信,用于通過內(nèi)核進(jìn)行進(jìn)程間通信,也可以用于線程間的通信。source1能夠?qū)un loop喚醒。Core Foundation中的CFMachPortCFMessagePort通過source1的方式實(shí)現(xiàn)。定義如下:

typedef struct {
    CFIndex version;//1代表source1
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#endif
} CFRunLoopSourceContext1;

其中

  • getPort函數(shù)指針提供一個(gè)獲取port的函數(shù)
  • perform函數(shù)是回調(diào)函數(shù)

有趣的是,CFMessagePort通常用于進(jìn)程間通信,如iOS越獄開發(fā),常用的場(chǎng)景是前端有一個(gè)UI程序用于界面展示,后端有一個(gè)daemo精靈程序用于任務(wù)處理。而官方文檔中有提到,CFMessagePort已不能再iOS7之后的系統(tǒng)中使用。來源。

CFRunLoopSourceRef的使用

Core Foundation提供了提供了與input sourc交互的函數(shù)

  • CFRunLoopSourceGetContext:創(chuàng)建source0或者source1變量
  • CFRunLoopSourceCreate:創(chuàng)建CFRunLoopSourceRef
  • CFRunLoopAddSource:添加CFRunLoopSourceRef到run loop中

CFRunLoopTimerRef

CFRunLoopTimerRef是和NSTimer toll-free bridge的計(jì)時(shí)器。Run loop timer并不一定可靠,如果加入的mode沒有運(yùn)行或者當(dāng)前run loop在執(zhí)行一段耗時(shí)的操作,run loop timer可能不會(huì)被觸發(fā)。而且Run loop timer的觸發(fā)時(shí)間依賴于計(jì)劃的時(shí)間間隔,而不是實(shí)際運(yùn)行的時(shí)間間隔,比如一個(gè)5秒重復(fù)的計(jì)時(shí)器,在第二次時(shí)延時(shí)了2秒,那么第三次的執(zhí)行時(shí)間不會(huì)改變,仍然是第15秒時(shí)執(zhí)行。

Run loop timer可以同時(shí)加入到多個(gè)run loop mode中,但中只會(huì)在第一個(gè)加入的run loop內(nèi)生效。

CFRunLoopTimerRef主要包含了interval參數(shù)和Callback回調(diào)方法。

Run loop timer關(guān)于時(shí)間計(jì)算的機(jī)制以后再深入了解

CFRunLoopObserverRef

CFRunLoopObserverRef是一個(gè)觀察者,它包含了一個(gè)回調(diào)指針和一個(gè)Activity參數(shù),用于表明接受的run loop事件,具體事件定義如下,當(dāng)run loop處于不同狀態(tài)時(shí),會(huì)通知obsever。

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
};

blocks

__CFRunLoop的定義中,還有兩個(gè)關(guān)于block鏈表的定義

struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;

Run loop還提供了一個(gè)CFRunLoopPerformBlock函數(shù)用于添加block,和source0類似,這個(gè)方法不會(huì)主動(dòng)喚醒run loop,需要調(diào)用CFRunLoopWakeUp函數(shù)主動(dòng)喚醒run loop。

CFRunLoopPerformBlock的定義如下

void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))

在run loop運(yùn)行的每一次循環(huán)中,會(huì)多次檢查當(dāng)前是否有需要執(zhí)行的block,如果有則會(huì)執(zhí)行傳入的block。

CFRunLoopGet

在通常開發(fā)時(shí),我們通常不需要關(guān)心run loop的生命周期。系統(tǒng)會(huì)自動(dòng)在主線程幫我們創(chuàng)建run loop對(duì)象。可以通過如下接口獲得主線程的run loop對(duì)象,該方法返回一個(gè)CFRunLoopRef實(shí)例。

CFRunLoopRef CFRunLoopGetMain(void);

當(dāng)我們創(chuàng)建一個(gè)新的線程時(shí),默認(rèn)是沒有初始化run loop對(duì)象的,需要調(diào)用函數(shù)獲取當(dāng)前線程的run loop對(duì)象。

CFRunLoopRef CFRunLoopGetCurrent(void);

如此看來,創(chuàng)建run loop對(duì)象的函數(shù)就是Core Foundation內(nèi)部實(shí)現(xiàn)的了,CFRunLoopGetMainCFRunLoopGetCurrent這兩個(gè)函數(shù)內(nèi)部都會(huì)調(diào)用同一個(gè)方法_CFRunLoopGet0(pthread_t t),來看下具體的代碼。

這里簡(jiǎn)單提一下TSD(thread specific data)的概念,在多線程環(huán)境中,因?yàn)閿?shù)據(jù)空間是共享的,所以全局變量也為所有線程所共有。所以當(dāng)需要一個(gè)僅在當(dāng)前線程中可以訪問的數(shù)據(jù)時(shí),使用TSD來存儲(chǔ)。TSD存儲(chǔ)的數(shù)據(jù)僅在當(dāng)前線程有效,但是可以跨函數(shù)訪問。

run loop對(duì)象就存儲(chǔ)在TSD中。

CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //__CFRunLoops是全局保存runloop對(duì)象的dict,首次運(yùn)行時(shí)初始化該dict
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        //創(chuàng)建全局ditc,并添加主線程的runloop 對(duì)象
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //通過__CFRunLoopCreate方法創(chuàng)建主線程的run loop對(duì)象
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        //將dict與__CFRunLoops指針互換,然后釋放ditc
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
    }
    //通過線程對(duì)象獲取對(duì)應(yīng)的runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //如果該線程還沒有創(chuàng)建runloop對(duì)象,那么初始化該線程的runloop對(duì)象
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
    }
    //判斷是否獲取當(dāng)前線程的runloop對(duì)象
    if (pthread_equal(t, pthread_self())) {
        //將run loop對(duì)象存放到TSD中
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            //在這里設(shè)置銷毀的回調(diào)方法,當(dāng)線程生命周期結(jié)束時(shí)銷毀runloop對(duì)象
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

該函數(shù)的流程如下

  1. 第一次進(jìn)入時(shí),會(huì)創(chuàng)建一個(gè)全局的dict,用于存儲(chǔ)每一個(gè)線程對(duì)應(yīng)的run loop對(duì)象,同時(shí)也會(huì)初始化主線程的run loop對(duì)象。
  2. 根據(jù)傳入的線程獲取對(duì)應(yīng)的run loop對(duì)象,若為空,則創(chuàng)建一個(gè)run loop對(duì)象,并添加到全局dict中。同時(shí)也會(huì)將這個(gè)run loop放到對(duì)應(yīng)線程的TSD中并設(shè)置一個(gè)線程結(jié)束時(shí)的銷毀函數(shù)回調(diào)。

通過上面的函數(shù),我們可以獲取到當(dāng)前線程的run loop對(duì)象。在執(zhí)行前面所講的添加source/timer/observers函數(shù)時(shí),都需要傳入run loop對(duì)象。

系統(tǒng)會(huì)啟動(dòng)主線程的run loop的運(yùn)行,對(duì)于其他線程,需要我們?cè)讷@取run loop對(duì)象后主動(dòng)啟動(dòng)。

CFRunLoopRun

首先通過一張圖,了解下CFRunLoopRun的主要邏輯。

image.png

PS:圖中左邊的source0(port)應(yīng)該是source1(port)

在Core Foundation提供了兩個(gè)函數(shù)供我們啟動(dòng)run loop,CFRunLoopRunCFRunLoopRunInMode,函數(shù)聲明如下:

//無參數(shù),直接啟動(dòng)run loop,運(yùn)行在default mode
void CFRunLoopRun(void);
//設(shè)置run loop運(yùn)行的mode,有效期以及是否在運(yùn)行source 0事件后直接退出,返回值為run loop退出的原因字段
SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

這兩個(gè)函數(shù)都會(huì)調(diào)用CFRunLoopRunSpecific函數(shù),該函數(shù)實(shí)現(xiàn)如下,注釋中的數(shù)字對(duì)應(yīng)圖中的順序

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    //判斷mode中是否有source,timer或者block事件,如果沒有,run loop會(huì)立即退出
    if (__CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        return kCFRunLoopRunFinished;
    }
    int32_t retVal = kCFRunLoopRunFinished;
    //1. run loop即將進(jìn)入,通知Observers
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
      do {
        //2. 通知觀察者,即將觸發(fā)計(jì)時(shí)器
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //3. 通知觀察者即將觸發(fā)source0(非port based)輸入源
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //處理blocks
        __CFRunLoopDoBlocks(rl, rlm);
        //4. 處理source0,也就是非port based輸入源
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            //處理完source0后會(huì)再處理下blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        //5. 檢查是否有需要處理的source1事件,通常是系統(tǒng)級(jí)事件,倒數(shù)第三個(gè)參數(shù)傳0表示立即返回
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            //跳轉(zhuǎn)到handle_msg直接去處理source1事件
            goto handle_msg;
        }
        
        //沒有source1則直接進(jìn)入睡眠
        //6. 通知觀察者Runloop即將進(jìn)入睡眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        //7. 調(diào)用系統(tǒng)內(nèi)核方法mach_msg,切換到內(nèi)核態(tài)接受消息
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        //當(dāng)有source1,timer或者手動(dòng)喚醒時(shí),會(huì)退出睡眠態(tài)
        //8. 通知觀察者Runloop退出等待
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:;
        //9. 退出睡眠后,需要處理些事件
        //計(jì)時(shí)器需要觸發(fā)
        if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            //觸發(fā)計(jì)時(shí)器
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                //計(jì)算下一次觸發(fā)的時(shí)間
                __CFArmNextTimerInMode(rlm, rl);
            }
        } else if (livePort == dispatchPort) {
            //如果有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);
        }
        //再次執(zhí)行一次block回調(diào)
        __CFRunLoopDoBlocks(rl, rlm);
        
        //判斷是否需要退出run loop
        if (sourceHandledThisLoop && stopAfterHandle) {
            //啟動(dòng)run loop時(shí)設(shè)置了執(zhí)行source0后立即退出
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            //啟動(dòng)run loop時(shí)設(shè)置了超時(shí)時(shí)間
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            //run loop被設(shè)置為停止
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            //run loop mode被設(shè)置為停止
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            //當(dāng)前mode被移除了
            retVal = kCFRunLoopRunFinished;
        }
    } while (0 == retVal);
    //10. run loop已經(jīng)退出,通知observers
      __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  
    return retVal;
}

通過代碼可以了解到

  • run loop通過do…while循環(huán)實(shí)現(xiàn),只要不滿足退出的條件,run loop就會(huì)睡眠或者運(yùn)行。
  • run loop需要添加source,timer或者block事件才能運(yùn)行,否則會(huì)直接退出
  • run loop進(jìn)入休眠時(shí)調(diào)用mach_msg函數(shù)切換到內(nèi)核態(tài)(當(dāng)我們?cè)赼pp運(yùn)行時(shí)點(diǎn)擊暫停,就可以看到調(diào)用棧停留在mach_msg_trap()這個(gè)方法)
image.png

PerformSelecter

當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒有 RunLoop,則這個(gè)方法會(huì)失效。

當(dāng)調(diào)用 performSelector:onThread: 時(shí),實(shí)際上其會(huì)創(chuàng)建一個(gè)source0 加到對(duì)應(yīng)的線程去,同樣的,如果對(duì)應(yīng)線程沒有 RunLoop 該方法也會(huì)失效。

蘋果對(duì)RunLoop的應(yīng)用

了解了RunLoop的實(shí)現(xiàn),我們看一看蘋果如何使用RunLoop的。該節(jié)主要參考深入理解RunLoop,這里僅做部分展開。

viewDidLoad方法中增加斷點(diǎn),打印[NSRunLoop currentRunLoop]可以打印出當(dāng)前線程的runLoop對(duì)象的一些詳細(xì)信息

<CFRunLoop 
current mode = kCFRunLoopDefaultMode,
common modes = {
     "UITrackingRunLoopMode"
     "kCFRunLoopDefaultMode"
}

common mode items = {
  //source 0
    CFRunLoopSource { 
    order = -1, version = 0, callout = PurpleEventSignalCallback 
  }
    CFRunLoopSource { 
    order = -1, version = 0, callout = __handleEventQueue 
  }
    CFRunLoopSource { 
    order = 0, version = 0, callout = FBSSerialQueueRunLoopSourceHandler 
  }
    CFRunLoopSource {
    order = -2, version = 0, callout = __handleHIDEventFetcherDrain
  }

  //source 1
    CFRunLoopSource {
    order = 0, port = 43275
  }
    CFRunLoopSource {
    order = -1, version = 1, callout = PurpleEventCallback
  }
    CFRunLoopSource {
    order = 0, port = 42755
  }

  //UI繪制相關(guān)
    CFRunLoopObserver {
    activities = 0xa0, order = 1999000, callout = _beforeCACommitHandler
  }
    CFRunLoopObserver {
    activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
  }
    CFRunLoopObserver {
    activities = 0xa0, order = 2001000, callout = _afterCACommitHandler
  }

  //自動(dòng)釋放池(auto release pool)相關(guān)
    CFRunLoopObserver {
    activities = 0x1, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
  }
    CFRunLoopObserver {
    activities = 0xa0, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
  }

    CFRunLoopObserver {
    activities = 0x20, order = 0, callout =_UIGestureRecognizerUpdateObserver
  }
}

modes = {
    //RunLoop包含的mode以及mode中的observers/sources/timers
}

AutoreleasePool

蘋果在主線程RunLoop的CommonModes中注冊(cè)了兩個(gè)Observer,其中回調(diào)函數(shù)都是_wrapRunLoopWithAutoreleasePoolHandler

<CFRunLoopObserver>{
  order = -2147483647, activities = 0x1, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
<CFRunLoopObserver>{
  order = 2147483647, activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler
}

第一個(gè)Observer設(shè)置了最高優(yōu)先級(jí)-2147483647,在進(jìn)入RunLoop時(shí)會(huì)觸發(fā)(0x1)。

第二個(gè)Observer設(shè)置了最低優(yōu)先級(jí)2147483647,在RunLoop進(jìn)入睡眠或者退出時(shí)觸發(fā)(0xa0)。

_wrapRunLoopWithAutoreleasePoolHandler

通過在XCode中添加Symbolic Breakpoint增加斷點(diǎn),查看_wrapRunLoopWithAutoreleasePoolHandler對(duì)應(yīng)匯編的代碼。

image.png

啟動(dòng)App后,會(huì)進(jìn)入該斷點(diǎn),可以看到代碼中有兩處地方會(huì)跳轉(zhuǎn)到其他函數(shù),分別是NSPushAutoreleasePoolNSPopAutoreleasePool,根據(jù)名字可以看出,一個(gè)是autlreleasePool的創(chuàng)建,另一個(gè)是autlreleasePool的釋放,推測(cè)在該函數(shù)中會(huì)判斷RunLoop當(dāng)前的狀態(tài),然后執(zhí)行不同的函數(shù)。

然后我們繼續(xù)對(duì)NSPushAutoreleasePoolNSPopAutoreleasePool添加斷點(diǎn),最終調(diào)用函數(shù)分別是objc_autoreleasePoolPushobjc_autoreleasePoolPop

第一個(gè)Observer在進(jìn)入RunLoop時(shí),創(chuàng)建autoreleasePool,其order=-2147483647保證了是所有回調(diào)之前調(diào)用。

第二個(gè)Observer在RunLoop休眠時(shí),首先釋放autoreleasePool,然后創(chuàng)建autoreleasePool;在RunLoop退出時(shí),釋放autoreleasePool,其order=2147483647保證了是所有回調(diào)之后調(diào)用。

總結(jié)

本文主要對(duì)Core Foundation框架中的RunLoop源碼進(jìn)行學(xué)習(xí),了解了RunLoop的實(shí)現(xiàn)原理,后續(xù)會(huì)對(duì)RunLoop的應(yīng)用進(jìn)一步探索研究。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Runloop是iOS和OSX開發(fā)中非常基礎(chǔ)的一個(gè)概念,從概念開始學(xué)習(xí)。 RunLoop的概念 -般說,一個(gè)線程一...
    小貓仔閱讀 1,111評(píng)論 0 1
  • 轉(zhuǎn)自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_閱讀 1,682評(píng)論 0 5
  • RunLoop 的概念 一般來講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線...
    Mirsiter_魏閱讀 675評(píng)論 0 2
  • 前言 RunLoop是iOS和OSX開發(fā)中非?;A(chǔ)的一個(gè)概念,這篇文章將從CFRunLoop的源碼入手,介紹Run...
    暮年古稀ZC閱讀 2,411評(píng)論 1 19
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術(shù) RunLoop 是 iOS 和 ...
    橙娃閱讀 968評(píng)論 1 2

友情鏈接更多精彩內(nèi)容