iOS RunLoop(1)-底層解析

1. RunLoop定義

RunLoop:運(yùn)行循環(huán),在程序運(yùn)行過程中循環(huán)做一些事情。所涉及的范疇包括:
① 定時(shí)器(Timer)、PerformSelector;
② GCD Async Main Queue;
③ 事件響應(yīng)、手勢(shì)識(shí)別、界面刷新;
④ 網(wǎng)絡(luò)請(qǐng)求;
⑤ AutoreleasePool。

runlooper示意圖.png

通常情況下如果我們創(chuàng)建一個(gè)Command Line Tool項(xiàng)目,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

程序在執(zhí)行到Line13之后,馬上會(huì)執(zhí)行Line15,然后程序退出,因?yàn)榇硕未a中沒有runloop.
但是在我們的iOS項(xiàng)目中并不是和Command Line Tool一樣寫main函數(shù)的.

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我們可以通過runloop的源碼得知runloop的原理,所以我們模擬可以寫代碼模擬

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int retVal = 0;
        do {
            // 睡眠中等待消息
            int message = sleep_and_wait();
            // 處理消息
            retVal = process_message(message);
        } while (0 == retVal);
        
        return 0;
    }
}

有了RunLoop,程序并不會(huì)馬上退出,而是保持運(yùn)行狀態(tài)。RunLoop可以處理App中的各種事件(比如觸摸事件、定時(shí)器事件等);RunLoop可以在該做事的時(shí)候做事,該休息的時(shí)候休息,從而節(jié)約CPU資源,提高程序性能。
iOS中提供兩套API來訪問和使用RunLoop,分別為Foundation框架下面的NSRunLoop和Core Foundation下面的CFRunLoopRef。
NSRunLoop和CFRunLoopRef都是RunLoop對(duì)象,其中NSRunLoop是基于CFRunLoopRef的一層OC包裝(參考圖NSRunLoop是基于CFRunLoopRef的一層OC包裝.png),CFRunLoopRef是開源的,開源地址。

參考圖NSRunLoop是基于CFRunLoopRef的一層OC包裝.png

上圖中,touch事件在主線程中觸發(fā),主線程所對(duì)應(yīng)的RunLoop為mainRunLoop,通過OC與C語言的API訪問RunLoop地址,可見兩者指針不同,但是NSLog(@"%@",[NSRunLoop currentRunLoop]);輸出的RunLoop詳細(xì)信息發(fā)現(xiàn)NSRunLoop本質(zhì)也是一個(gè)CFRunLoop,并且其地址與CFRunLoopGetMain()相同,進(jìn)而可以說明NSRunLoop是基于CFRunLoopRef的一層OC包裝。

2. RunLoop與線程的關(guān)系

平時(shí)都是通過:獲取當(dāng)前線程CFRunLoopGetCurrent();,獲取主線程CFRunLoopGetMain();.下面我們就來分析一下這兩個(gè)函數(shù)都做了什么,我們可以通過CFRunLoop源碼來看一下.

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

在調(diào)用CFRunLoopGetCurrent(),CFRunLoopGetMain()函數(shù)時(shí),函數(shù)內(nèi)部都會(huì)調(diào)用_CFRunLoopGet0(pthread_t t)函數(shù),并傳入不同的線程作為參數(shù)??梢娬{(diào)用CFRunLoopGetMain()時(shí),傳入的是主線程,調(diào)用CFRunLoopGetCurrent()時(shí)傳入的是當(dāng)前的線程(子線程或主線程)。接下來繼續(xù)看看_CFRunLoopGet0(pthread_t t)函數(shù):

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
    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);
        __CFLock(&loopsLock);
    }
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        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;
}

透過_CFRunLoopGet0(pthread_t t)源碼,如果__CFRunLoops不存在,系統(tǒng)就會(huì)利用當(dāng)前的主線程"pthread_main_thread_np()"創(chuàng)建一個(gè)mainLoop,也就是“mainRunLoop”,然后將創(chuàng)建RunLoop的線程作為key,RunLoop作為value添加到一個(gè)全局的字典CFDictionarySetValue中。最后獲取"pthread_t"線程對(duì)應(yīng)的loop并返回loop。如果"pthread_t"不是主線程,用同樣的方法創(chuàng)建一個(gè)新的RunLoop,即"newLoop",同樣將"newLoop"存放到字典中然后返回newLoop。所以關(guān)于RunLoop與線程的關(guān)系可以總結(jié)如下:

  • 每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象;
  • RunLoop保存在一個(gè)全局的Dictionary里 ,線程作為key , Runloop作為value;
  • 線程剛創(chuàng)建時(shí)并沒有RunLoop對(duì)象, RunLoop會(huì)在第一次獲取它時(shí)創(chuàng)建;
  • RunLogp會(huì)在線程結(jié)束時(shí)銷毀;
  • 主線程的RunLoop已經(jīng)自動(dòng)獲取(創(chuàng)建) ,子線程默認(rèn)沒有開啟Runloop。

3. RunLoop對(duì)象結(jié)構(gòu)

獲取RunLoop對(duì)象

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

我們可以用NSLog(@"%@",[NSRunLoop currentRunLoop]);方法打印Runloop對(duì)象,打印出RunLoop大概包含current mode、common modes、common mode items、modes這些信息。實(shí)際上從開源的文檔里,RunLoop包含的成員遠(yuǎn)不止上面這些,具體包含內(nèi)容如下圖:

RunLoop結(jié)構(gòu)體成員.png

其中比較重要的是"_modes"這個(gè)成員,它是一個(gè)集合類型,里面存放了多個(gè)CFRunLoopMode類型的mode成員,一個(gè)RunLoop可以有多個(gè)mode,這些mode都包含在"_modes"里。CFRunLoopMode結(jié)構(gòu)體如下:

CFRunLoopMode 結(jié)構(gòu)體.png

CFRunLoopModeRef的定義

  • CFRunLoopModeRef代表RunLoop的運(yùn)行模式;
  • 一個(gè)RunLoop包含若干個(gè)Mode,每個(gè)Mode又包含若干個(gè)Source0/Source1/Timer/Observer;
  • RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode
    如果需要切換Mode,只能退出當(dāng)前Loop,再重新選擇一個(gè)Mode進(jìn)入:
    ?????①為什么要先退出再進(jìn)入;
    ?????②不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響;
  • 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會(huì)立馬退出

4. CFRunLoopModeRef的常用的兩種model

kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行;
UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響;
其實(shí)還有一種特殊的模式kCFRunLoopCommonModes 的模式NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式 NSRunLoopCommonModes并不是一個(gè)真的模式,它只是一個(gè)標(biāo)記;

每一種mode里面都有source0、source1、observers、timers,他們主要負(fù)責(zé)的內(nèi)容如下:

  • Source0
    觸摸事件處理
    performSelector:onThread:
  • Source1
    基于Port的線程間通信
    系統(tǒng)事件捕捉
  • Timers
    NSTimer
    performSelector:withobiect:afterDelay;
  • Observers
    用于監(jiān)聽Runloop的狀態(tài)
    UI刷新( BeforeWaiting)
    Autorelease pool ( BeforeWaiting )

兩種方式監(jiān)聽RunLoop狀態(tài):

方式①:
- (void)viewDidLoad {
    
    [super viewDidLoad];
    // 創(chuàng)建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
        switch (activity) {
            case kCFRunLoopEntry: {
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopBeforeTimers:{
                NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
                  CFRelease(mode);
                break;
            }
            case kCFRunLoopBeforeSources:{
                NSLog(@"kCFRunLoopBeforeSources - %@", mode);
                  CFRelease(mode);
                break;
            }
            case kCFRunLoopBeforeWaiting:{
                NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
                  CFRelease(mode);
                break;
            }
            case kCFRunLoopAfterWaiting:{
                NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
                  CFRelease(mode);
                break;
            }
            case kCFRunLoopExit: {
                NSLog(@"kCFRunLoopExit - %@", mode);
                CFRelease(mode);
                break;
            }
                
            default:
                break;
        }
    });
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}


方式②:
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
    switch (activity) {
        case kCFRunLoopEntry:
             NSLog(@"kCFRunLoopEntry - %@", mode);
            break;
        case kCFRunLoopBeforeTimers:
             NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
            break;
        case kCFRunLoopBeforeSources:
             NSLog(@"kCFRunLoopBeforeSources - %@", mode);
            break;
        case kCFRunLoopBeforeWaiting:
             NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
            break;
        case kCFRunLoopAfterWaiting:
             NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
            break;
        case kCFRunLoopExit:
             NSLog(@"kCFRunLoopExit - %@", mode);
            break;
        default:
            break;
    }
     CFRelease(mode);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // kCFRunLoopCommonModes默認(rèn)包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
    
    // 創(chuàng)建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}

CFRunLoopObserverRef的幾種狀態(tài)

/* Run Loop Observer Activities */
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
          KCFRunLoopAllActivities = 0x0FFFFFFU
}
                            想了解更多iOS學(xué)習(xí)知識(shí)請(qǐng)聯(lián)系:QQ(814299221)
最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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