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

一.Runloop是什么

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

二.Runloop的基本作用

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

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

3.節(jié)省CPU資源,提高程序性能,當(dāng)程序運(yùn)行起來的時(shí)候,線程沒有接收到任何任務(wù),其對(duì)應(yīng)的Runloop就會(huì)告知CPU,我現(xiàn)在沒事可做,我要去休息,當(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基本源碼

我們先來看一下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來判斷是否要繼續(xù)執(zhí)行下去,當(dāng)result停止或者結(jié)束時(shí),Runloop便會(huì)跳出循環(huán),否則就會(huì)繼續(xù)執(zhí)行。

四.Runloop對(duì)象

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

如何獲取Runloop對(duì)象呢?蘋果是不允許直接創(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)系

以前蘋果的多線程有兩種pthread_tNSThread,pthread_tNSThread是一一對(duì)應(yīng)的。比如,你可以通過 pthread_main_thread_np()[NSThread mainThread]來獲取主線程;也可以通過pthread_self()[NSThread currentThread]來獲取當(dāng)前線程。CFRunLoop是基于pthread來管理的。我們先看一下Runloop的源碼。

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

    __CFLock(&loopsLock);
    //程序第一次進(jìn)來判斷是否存在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)來的線程作為key來獲取
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    //如果沒取到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;
}

通過源碼可以得出以下結(jié)論,每條線程都有一個(gè)與之一一對(duì)應(yīng)的Runloop,線程和Runloop會(huì)保存在一個(gè)全局字典里,系統(tǒng)會(huì)自動(dòng)創(chuàng)建主線程的Runloop,子線程的Runloop則需要手動(dòng)創(chuàng)建,通過[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原理,分類為什么不能添加屬性等問題,去看一下相關(guān)源碼立刻就會(huì)恍然大悟,因此我個(gè)人在學(xué)習(xí)的時(shí)候也很熱衷于閱讀相關(guān)源碼,那么現(xiàn)在就讓我們來看一下Runloop的結(jié)構(gòu)體。

先來看一下關(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

我們可以通過點(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)證

再來看一下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)鍵的類,我們分別來說一下他們的作用

  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)入,這樣做主要是為了分隔開不同組的Source、Timer、Observer,讓其互不影響。如果Mode里沒有任何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è)老生常談的問題,我們使用NSTimer定時(shí)器,每隔一段時(shí)間執(zhí)行一些事件,在這個(gè)過程中我們滑動(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是用來觀察Runloop運(yùn)行狀態(tài)的類,看一下它的相關(guān)代碼
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
     //創(chuàng)建監(jiān)聽者
     /*
     第一個(gè)參數(shù) CFAllocatorRef allocator:分配存儲(chǔ)空間 CFAllocatorGetDefault()默認(rèn)分配
     第二個(gè)參數(shù) CFOptionFlags activities:要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽所有狀態(tài)
     第三個(gè)參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽 NO:不持續(xù)
     第四個(gè)參數(shù) CFIndex order:優(yōu)先級(jí),一般填0即可
     第五個(gè)參數(shù) :回調(diào) 兩個(gè)參數(shù)observer:監(jiān)聽者 activity:監(jiān)聽的事件
     */
     /*
     所有事件
     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醒來了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
                
            default:
                break;
        }
    });
    
    // 給RunLoop添加監(jiān)聽者
    /*
     第一個(gè)參數(shù) CFRunLoopRef rl:要監(jiān)聽哪個(gè)RunLoop,這里監(jiān)聽的是主線程的RunLoop
     第二個(gè)參數(shù) CFRunLoopObserverRef observer 監(jiān)聽者
     第三個(gè)參數(shù) CFStringRef mode 要監(jiān)聽RunLoop在哪種運(yùn)行模式下的狀態(tài)
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     /*
     CF的內(nèi)存管理(Core Foundation)
     凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來的對(duì)象,都需要在最后做一次release
     GCD本來在iOS6.0之前也是需要我們釋放的,6.0之后GCD已經(jīng)納入到了ARC中,所以我們不需要管了
     */
    CFRelease(observer);
}

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


Runloop流程

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

八.Runloop的處理邏輯

先來看看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);
}

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

// 經(jīng)過精簡(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);
        
        // 醒來
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知Observers:已經(jīng)喚醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg: // 看看是誰喚醒了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é)果,來決定怎么做,為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)行的過程中一直存在,就比如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開啟時(shí),就會(huì)自動(dòng)創(chuàng)建一個(gè)自動(dòng)釋放池,當(dāng)RunLoop在休息之前會(huì)釋放掉自動(dòng)釋放池的東西,然后重新創(chuàng)建一個(gè)新的空的自動(dòng)釋放池,當(dāng)RunLoop被喚醒重新開始跑圈時(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)系作者
【社區(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)容

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

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