Runloop相關(guān)閱讀筆記

一.Runloop是什么

通俗點(diǎn)來(lái)說(shuō)往堡,我們有一個(gè)線程,當(dāng)我們需要它處理事件時(shí)翠拣,它要隨時(shí)啟動(dòng)版仔,我們不需要它時(shí),它要隨時(shí)待命误墓,如果我們每次運(yùn)行完畢之后這個(gè)線程直接關(guān)閉了蛮粮,下一次運(yùn)行需要再重新創(chuàng)建一個(gè)新的線程,那無(wú)疑會(huì)大大的消耗CPU的性能优烧。因此我們就需要一種機(jī)制蝉揍,讓線程隨時(shí)待命,執(zhí)行任務(wù)完畢進(jìn)入休眠狀態(tài)畦娄,等待下一次喚醒又沾,在macOSiOS系統(tǒng)中,就是通過(guò)Runloop來(lái)對(duì)線程進(jìn)行管理的熙卡。

二.Runloop的基本作用

1.保證程序的持續(xù)運(yùn)行杖刷,程序一啟動(dòng)就會(huì)創(chuàng)建主線程,主線程一開(kāi)始執(zhí)行就會(huì)創(chuàng)建對(duì)應(yīng)的Runloop驳癌,保證線程不會(huì)被銷毀滑燃,使其平穩(wěn)運(yùn)行。

2.處理APP中的各種事件颓鲜,比如我們的觸摸事件表窘,定時(shí)器事件,selector事件甜滨。

3.節(jié)省CPU資源乐严,提高程序性能,當(dāng)程序運(yùn)行起來(lái)的時(shí)候衣摩,線程沒(méi)有接收到任何任務(wù)昂验,其對(duì)應(yīng)的Runloop就會(huì)告知CPU,我現(xiàn)在沒(méi)事可做艾扮,我要去休息既琴,當(dāng)有事件產(chǎn)生時(shí),需要這個(gè)線程去執(zhí)行泡嘴,Runloop就會(huì)立刻告之其所對(duì)應(yīng)的線程去執(zhí)行任務(wù)甫恩。

Runloop運(yùn)行原理

從圖中我們可以看到,當(dāng)有事件產(chǎn)生酌予,或者定時(shí)器方法需要執(zhí)行時(shí)填物,Runloop就會(huì)將對(duì)應(yīng)的任務(wù)安排給相應(yīng)的處理方去執(zhí)行纹腌。

三.Runloop基本源碼

我們先來(lái)看一下Runloop的基本執(zhí)行源碼

void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

這段源碼就是一個(gè)非常簡(jiǎn)單的do-while循環(huán),CFRunLoopRun會(huì)根據(jù)result來(lái)判斷是否要繼續(xù)執(zhí)行下去滞磺,當(dāng)result停止或者結(jié)束時(shí)升薯,Runloop便會(huì)跳出循環(huán),否則就會(huì)繼續(xù)執(zhí)行击困。

四.Runloop對(duì)象

RunloopCocoa Foundation中是NSRunLoop對(duì)象涎劈,而在Core Foundation中則是 CFRunLoopRef對(duì)象,我們解讀的源碼是CFRunLoopRef阅茶,Runloop的代碼是開(kāi)源的蛛枚,可以前往蘋(píng)果官網(wǎng)的地址進(jìn)行下載:https://opensource.apple.com/tarballs/

如何獲取Runloop對(duì)象呢?蘋(píng)果是不允許直接創(chuàng)建Runloop對(duì)象的脸哀,但是給我們提供了相應(yīng)的方法獲取線程的Runloop對(duì)象

Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象

Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象

五.線程與Runloop的關(guān)系

以前蘋(píng)果的多線程有兩種pthread_tNSThread蹦浦,pthread_tNSThread是一一對(duì)應(yīng)的。比如撞蜂,你可以通過(guò) pthread_main_thread_np()[NSThread mainThread]來(lái)獲取主線程盲镶;也可以通過(guò)pthread_self()[NSThread currentThread]來(lái)獲取當(dāng)前線程。CFRunLoop是基于pthread來(lái)管理的蝌诡。我們先看一下Runloop的源碼溉贿。

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }

    __CFLock(&loopsLock);
    //程序第一次進(jìn)來(lái)判斷是否存在runloop
    if (!__CFRunLoops) {
        //不存在runloop
        __CFUnlock(&loopsLock);
    //創(chuàng)建一個(gè)字典,用于管理保存線程以及其對(duì)應(yīng)的runloop
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    //使用主線程創(chuàng)建一個(gè)主循環(huán)(mainLoop)
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    //為字典賦值浦旱,主線程為字典的key宇色,runloop為字典的value
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }

    //從字典中獲取runloop,使用傳進(jìn)來(lái)的線程作為key來(lái)獲取
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    //如果沒(méi)取到runloop
    if (!loop) {
    //新建一個(gè)runloop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        //新建runloop成功颁湖,將runloop存入字典
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

通過(guò)源碼可以得出以下結(jié)論宣蠕,每條線程都有一個(gè)與之一一對(duì)應(yīng)的Runloop線程和Runloop會(huì)保存在一個(gè)全局字典里甥捺,系統(tǒng)會(huì)自動(dòng)創(chuàng)建主線程的Runloop抢蚀,子線程的Runloop則需要手動(dòng)創(chuàng)建,通過(guò)[NSRunLoop currentRunLoop]涎永,Runloop在獲取時(shí)創(chuàng)建思币,在線程結(jié)束時(shí)銷毀鹿响。

六.Runloop的結(jié)構(gòu)體

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

其實(shí)很多我們平時(shí)難以理解的概念羡微,一旦進(jìn)入到結(jié)構(gòu)體層面去分析就會(huì)變得很好理解,比如Block原理惶我,分類為什么不能添加屬性等問(wèn)題妈倔,去看一下相關(guān)源碼立刻就會(huì)恍然大悟,因此我個(gè)人在學(xué)習(xí)的時(shí)候也很熱衷于閱讀相關(guān)源碼绸贡,那么現(xiàn)在就讓我們來(lái)看一下Runloop的結(jié)構(gòu)體盯蝴。

先來(lái)看一下關(guān)鍵的成員變量

CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;

CFRunLoopModeRef其實(shí)是指向__CFRunLoopMode結(jié)構(gòu)體的指針毅哗,我們?cè)倏匆幌?code>CFRunLoopMode的結(jié)構(gòu)體

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

照例篩選出最關(guān)鍵的幾個(gè)成員變量

CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;

Runloop在進(jìn)行運(yùn)行的時(shí)候是會(huì)選擇對(duì)應(yīng)模式的,也就是__CFRunLoopMode捧挺,每個(gè)Mode又有對(duì)應(yīng)的source0/source1/observers/timers虑绵,每次Runloop運(yùn)行的時(shí)候只能選擇一個(gè)Mode作為currentMode

六.source0/source1/observers/timers

1.source0:處理的是App內(nèi)部的事件闽烙、App自己負(fù)責(zé)管理翅睛,如按鈕點(diǎn)擊事件等
2.source1:由RunLoop和內(nèi)核管理黑竞,Mach Port驅(qū)動(dòng)捕发,如CFMachPort、CFMessagePort很魂。

我們可以通過(guò)點(diǎn)擊事件驗(yàn)證一下

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UITouch * touch = touches.anyObject;//獲取觸摸對(duì)象
    NSLog(@"%@",@(touch.tapCount));//短時(shí)間內(nèi)的點(diǎn)擊次數(shù)
}

在點(diǎn)擊方法中打斷點(diǎn)扎酷,然后在控制臺(tái)輸入bt即可看到完整的堆棧信息

source0的驗(yàn)證

同樣的performSelector也會(huì)觸發(fā)source0,我們用代碼驗(yàn)證一下

[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];

在test方法中打斷點(diǎn)遏匆,控制臺(tái)輸入bt查看堆棧信息


performSelector驗(yàn)證

再來(lái)看一下timer的驗(yàn)證法挨,調(diào)用NSTimer方法

[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
    NSLog(@"NSTimer ---- timer調(diào)用了");
}];

控制臺(tái)驗(yàn)證


NSTimer驗(yàn)證

Observer:告知外界RunLoop狀態(tài)的更改

七.Runloop相關(guān)類以及作用

Runloop的.h文件中我們可以看到,聲明了四個(gè)類

typedef struct __CFRunLoop * CFRunLoopRef;

typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

CFRunloop中又包含有一個(gè)關(guān)鍵的Mode

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

這五個(gè)類就是Runloop實(shí)現(xiàn)的最關(guān)鍵的類拉岁,我們分別來(lái)說(shuō)一下他們的作用

  1. CFRunLoopModeRef坷剧,這個(gè)代表了Runloop的運(yùn)行模式,一個(gè)Runloop包含若干個(gè)Mode喊暖,每個(gè)Mode又包含若干Source惫企,Timer,Observer陵叽,每次Runloop啟動(dòng)時(shí)狞尔,只能指定其中一個(gè)Mode,這個(gè)Mode被稱作 CurrentMode巩掺,如果需要切換Mode偏序,只能退出RunLoop,再重新指定一個(gè)Mode進(jìn)入胖替,這樣做主要是為了分隔開(kāi)不同組的Source研儒、Timer、Observer独令,讓其互不影響端朵。如果Mode里沒(méi)有任何Source0/Source1/Timer/ObserverRunLoop會(huì)立馬退出燃箭。
CFRunLoopModeRef示意圖

一個(gè)Mode可以有多個(gè)Source冲呢,Observer,Timer招狸。但是必須有一個(gè)Source或者Timer敬拓,不然Mode為空邻薯,Runloop會(huì)直接退出。

系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode

1. kCFRunLoopDefaultMode:App的默認(rèn)Mode乘凸,通常主線程是在這個(gè)Mode下運(yùn)行
2. UITrackingRunLoopMode:界面跟蹤 Mode厕诡,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
3. UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode营勤,啟動(dòng)完成后就不再使用木人,會(huì)切換到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
5. kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode冀偶,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用醒第,并不是一種真正的Mode

有一個(gè)老生常談的問(wèn)題,我們使用NSTimer定時(shí)器进鸠,每隔一段時(shí)間執(zhí)行一些事件稠曼,在這個(gè)過(guò)程中我們滑動(dòng)ScrollView,定時(shí)器是會(huì)收到影響的客年。

因?yàn)槿绻覀冊(cè)谥骶€程使用定時(shí)器霞幅,此時(shí)RunLoop的ModekCFRunLoopDefaultMode,即定時(shí)器屬于kCFRunLoopDefaultMode量瓜,那么此時(shí)我們滑動(dòng)ScrollView時(shí)司恳,RunLoopMode會(huì)切換到UITrackingRunLoopMode,因此在主線程的定時(shí)器就不在管用了绍傲,調(diào)用的方法也就不再執(zhí)行了扔傅,當(dāng)我們停止滑動(dòng)時(shí),RunLoop的Mode切換回kCFRunLoopDefaultMode烫饼,所以NSTimer就又管用了猎塞。

如果我們希望在滑動(dòng)界面時(shí),計(jì)時(shí)器仍然有效杠纵,我們指定Runloop的模式為kCFRunLoopCommonModes即可荠耽,因?yàn)?code>kCFRunLoopCommonModes包含kCFRunLoopDefaultMode,kCFRunLoopDefaultMode比藻。

  1. CFRunLoopObserverRef是用來(lái)觀察Runloop運(yùn)行狀態(tài)的類铝量,看一下它的相關(guān)代碼
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
     //創(chuàng)建監(jiān)聽(tīng)者
     /*
     第一個(gè)參數(shù) CFAllocatorRef allocator:分配存儲(chǔ)空間 CFAllocatorGetDefault()默認(rèn)分配
     第二個(gè)參數(shù) CFOptionFlags activities:要監(jiān)聽(tīng)的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽(tīng)所有狀態(tài)
     第三個(gè)參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽(tīng) NO:不持續(xù)
     第四個(gè)參數(shù) CFIndex order:優(yōu)先級(jí),一般填0即可
     第五個(gè)參數(shù) :回調(diào) 兩個(gè)參數(shù)observer:監(jiān)聽(tīng)者 activity:監(jiān)聽(tīng)的事件
     */
     /*
     所有事件
     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),   //   即將進(jìn)入RunLoop
     kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
     kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
     kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
     kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
     kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     };
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop進(jìn)入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要處理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要處理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒來(lái)了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
                
            default:
                break;
        }
    });
    
    // 給RunLoop添加監(jiān)聽(tīng)者
    /*
     第一個(gè)參數(shù) CFRunLoopRef rl:要監(jiān)聽(tīng)哪個(gè)RunLoop,這里監(jiān)聽(tīng)的是主線程的RunLoop
     第二個(gè)參數(shù) CFRunLoopObserverRef observer 監(jiān)聽(tīng)者
     第三個(gè)參數(shù) CFStringRef mode 要監(jiān)聽(tīng)RunLoop在哪種運(yùn)行模式下的狀態(tài)
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     /*
     CF的內(nèi)存管理(Core Foundation)
     凡是帶有Create银亲、Copy慢叨、Retain等字眼的函數(shù),創(chuàng)建出來(lái)的對(duì)象群凶,都需要在最后做一次release
     GCD本來(lái)在iOS6.0之前也是需要我們釋放的插爹,6.0之后GCD已經(jīng)納入到了ARC中哄辣,所以我們不需要管了
     */
    CFRelease(observer);
}

我們來(lái)看一下控制臺(tái)的輸出


Runloop流程

可以看到Observer可以監(jiān)聽(tīng)Runloop的運(yùn)行流程请梢。

八.Runloop的處理邏輯

先來(lái)看看Runloop的簡(jiǎn)化版源碼赠尾,我們分成幾個(gè)部分,一步一步的看

//這個(gè)是Runloop的外部函數(shù)毅弧,內(nèi)部會(huì)調(diào)用底層的CFRunLoopRunSpecific方法
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

接下來(lái)我們?cè)倏匆幌?strong>CFRunLoopRunSpecific方法

// 經(jīng)過(guò)精簡(jiǎn)的 CFRunLoopRunSpecific 函數(shù)代碼气嫁,其內(nèi)部會(huì)調(diào)用__CFRunLoopRun函數(shù)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知Observers : 進(jìn)入Loop
    // __CFRunLoopDoObservers內(nèi)部會(huì)調(diào)用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函數(shù)
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // 核心的Loop邏輯
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // 通知Observers : 退出Loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

CFRunLoopRunSpecific函數(shù)中最關(guān)鍵的__CFRunLoopRun函數(shù)源碼

// 精簡(jiǎn)后的 __CFRunLoopRun函數(shù),保留了主要代碼
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // 通知Observers:即將處理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
        
        // 通知Observers:即將處理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 處理Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 如果有Sources1够坐,就跳轉(zhuǎn)到handle_msg標(biāo)記處
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        
        // 通知Observers:即將休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        // 進(jìn)入休眠寸宵,等待其他消息喚醒
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        do {
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        
        // 醒來(lái)
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知Observers:已經(jīng)喚醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg: // 看看是誰(shuí)喚醒了RunLoop,進(jìn)行相應(yīng)的處理
        if (被Timer喚醒的) {
            // 處理Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }
        else if (被GCD喚醒的) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被Sources1喚醒的
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
        
        // 執(zhí)行Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 根據(jù)之前的執(zhí)行結(jié)果元咙,來(lái)決定怎么做梯影,為retVal賦相應(yīng)的值
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
        
    } while (0 == retVal);
    
    return retVal;
}

再附上一張流程圖
RunLoop流程圖

九.Runloop的應(yīng)用

(1)AFNetworking:在子線程中的任務(wù)都執(zhí)行完畢之后,子線程就會(huì)被銷毀庶香,但是我們遇到一些情況甲棍,希望這個(gè)線程在程序運(yùn)行的過(guò)程中一直存在,就比如AFN赶掖,網(wǎng)絡(luò)在運(yùn)行期間當(dāng)然要一直存在感猛,因此AFNetworking中就使用了一個(gè)方式,使線程一直保持存在狀態(tài)奢赂。

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
    [[NSThread currentThread] setName:@"AFNetworking"];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
}

(2)自動(dòng)釋放池RunLoop內(nèi)部有一個(gè)自動(dòng)釋放池陪白,當(dāng)RunLoop開(kāi)啟時(shí),就會(huì)自動(dòng)創(chuàng)建一個(gè)自動(dòng)釋放池膳灶,當(dāng)RunLoop在休息之前會(huì)釋放掉自動(dòng)釋放池的東西咱士,然后重新創(chuàng)建一個(gè)新的空的自動(dòng)釋放池,當(dāng)RunLoop被喚醒重新開(kāi)始跑圈時(shí)轧钓,Timer,Source等新的事件就會(huì)放到新的自動(dòng)釋放池中司致,當(dāng)RunLoop退出的時(shí)候也會(huì)被釋放。
注意:只有主線程RunLoop會(huì)默認(rèn)啟動(dòng)聋迎。也就意味著會(huì)自動(dòng)創(chuàng)建自動(dòng)釋放池脂矫,子線程需要在線程調(diào)度方法中手動(dòng)添加自動(dòng)釋放池。

參考文章:iOS底層原理總結(jié)霉晕,深入理解RunLoop

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末庭再,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牺堰,更是在濱河造成了極大的恐慌拄轻,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伟葫,死亡現(xiàn)場(chǎng)離奇詭異恨搓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門斧抱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)常拓,“玉大人,你說(shuō)我怎么就攤上這事辉浦∨В” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵宪郊,是天一觀的道長(zhǎng)掂恕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)弛槐,這世上最難降的妖魔是什么懊亡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮乎串,結(jié)果婚禮上斋配,老公的妹妹穿的比我還像新娘。我一直安慰自己灌闺,他們只是感情好艰争,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著桂对,像睡著了一般甩卓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蕉斜,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天逾柿,我揣著相機(jī)與錄音,去河邊找鬼宅此。 笑死机错,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的父腕。 我是一名探鬼主播弱匪,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼璧亮!你這毒婦竟也來(lái)了萧诫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤枝嘶,失蹤者是張志新(化名)和其女友劉穎帘饶,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體群扶,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡及刻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年镀裤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缴饭。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暑劝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茴扁,到底是詐尸還是另有隱情,我是刑警寧澤汪疮,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布峭火,位于F島的核電站,受9級(jí)特大地震影響智嚷,放射性物質(zhì)發(fā)生泄漏卖丸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一盏道、第九天 我趴在偏房一處隱蔽的房頂上張望稍浆。 院中可真熱鬧,春花似錦猜嘱、人聲如沸衅枫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弦撩。三九已至,卻和暖如春论皆,著一層夾襖步出監(jiān)牢的瞬間益楼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工点晴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留感凤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓粒督,卻偏偏與公主長(zhǎng)得像陪竿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屠橄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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

  • 轉(zhuǎn)自bireme萨惑,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_閱讀 1,374評(píng)論 0 5
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術(shù) RunLoop 是 iOS 和 ...
    橙娃閱讀 854評(píng)論 1 2
  • 原文地址:http://blog.ibireme.com/2015/05/18/runloop/ RunLoop ...
    大餅炒雞蛋閱讀 1,157評(píng)論 0 6
  • ======================= 前言 RunLoop 是 iOS 和 OSX 開(kāi)發(fā)中非常基礎(chǔ)的一個(gè)...
    i憬銘閱讀 879評(píng)論 0 4
  • 秋風(fēng)輕撫著臉頰 我在夢(mèng)的江南里 搖曳著小舟 追一片不知是不是 秋天落下的黃葉 蔚藍(lán)的天際 那一大片的云 我突然覺(jué)得...
    琳瑯5566閱讀 121評(píng)論 0 5