Runloop的內(nèi)部結(jié)構(gòu)與運(yùn)行原理

什么是Runloop

Runloop顧名思義,就是運(yùn)行循環(huán)。首先它根程序運(yùn)行過程有關(guān)系,其次它是一種轉(zhuǎn)圈圈的效果。但如果這么解釋,恐怕誰都聽不懂。

想要弄明白Runloop,就要搞清楚跟它有關(guān)聯(lián)的一些概念,以及它自身的運(yùn)作原理。

沒有Runloop的程序

我們通過Xcode新建一個(gè)命令行項(xiàng)目,main.m文件里的代碼如下

#import <Foundation/Foundation.h>

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

運(yùn)行程序,程序在執(zhí)行完代碼NSLog(@"Hello, World!");之后,就會(huì)通過 return 0;推出程序,這是一種線性的執(zhí)行流程,從上到下,很容易理解。

當(dāng)我們遇見Runloop

我們?cè)傩陆ㄒ粋€(gè)iOS項(xiàng)目,你看到的main.m文件是這個(gè)樣子的

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

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

我們很清楚,運(yùn)行程序之后,我們會(huì)進(jìn)入app的界面,然后app就不會(huì)退出了,會(huì)一直運(yùn)行著。你有沒有好奇過,同樣都是main函數(shù),為啥下面的這個(gè)能夠不退出呢?對(duì),這就是Runloop的功勞。

在命令行工程里面的main.m里面,是沒有加Runloop的,而iOS工程的main.m里面,其實(shí)在UIApplicationMain()這個(gè)方法中,系統(tǒng)加上了Runloop,讓程序可以一直循環(huán)運(yùn)行下去不退出。

這么解釋感覺還是有點(diǎn)僵硬,下面用偽代碼的形式來描述一下UIApplicationMain()內(nèi)部情況


說白了, Runloop其實(shí)就是一個(gè)do-while循環(huán),每次循環(huán)一圈,都會(huì)判斷一次retVal,決定是否結(jié)束循環(huán),繼續(xù)執(zhí)行循環(huán)外的代碼。

Runloop的作用簡(jiǎn)述

  • Runloopdo-while本質(zhì)說明它就是為了保持程序的持續(xù)運(yùn)行,不退出(試想一下手機(jī)上的app如果一打開就直接退出完事了,這個(gè)世界可能就沒有手機(jī)什么事了)
  • 保持程序持續(xù)運(yùn)行的根本目的,就是為了處理app的各種事件(觸摸事件,定時(shí)器事件),因?yàn)檫@些事件并不是編寫程序的時(shí)候就定好的,天知道用戶什么時(shí)候會(huì)點(diǎn)擊某個(gè)按鈕,對(duì)吧。因此想我們一開始說的那種線性的程序運(yùn)行方式,就無法處理這樣的場(chǎng)景。當(dāng)加上Runloop之后,在它的一次循環(huán)中,就可以去處理程序已經(jīng)接收到的各種事件,在處理這些事件的過程中,程序還會(huì)不斷的接受新來的事件,這樣,下次循環(huán)就可以繼續(xù)處理新來的事件。
  • 如果Runloop在某次循環(huán)之后,發(fā)現(xiàn)程序突然沒有收集到更多事件供它去處理,它就會(huì)休眠,實(shí)際上就是系統(tǒng)讓程序停在了Runloopdo-while循環(huán)里面的某一段代碼上(注意程序此時(shí)是停住,而不是退出哦),如果過了一會(huì)程序?yàn)?strong>Runloop接受到了新來的事件,它的do-while循環(huán)就會(huì)被系統(tǒng)重新激活以繼續(xù)運(yùn)行。這種機(jī)制的好處是,當(dāng)Runloop休眠的時(shí)候,CPU可以不用在它跟前侯著,轉(zhuǎn)而把時(shí)間精力分配給別的事務(wù),提高了CPU的使用效率。

你可把CPU想象成一個(gè)媽媽,Runloop就是剛出生的寶寶,寶寶醒的時(shí)候,媽媽就必須一直看著他,沒功夫去干別的事情,寶寶睡眠的時(shí)候,媽媽就可以抓緊時(shí)間去做一些別的事情了。如果沒有Runloop休眠機(jī)制,相當(dāng)于這個(gè)寶寶永遠(yuǎn)不會(huì)睡覺,那這個(gè)媽媽不久涼涼了嘛~~

深入理解Runloop對(duì)象

iOS中Runloop的API
  • Foundation: NSRunLoop
  • Core Foundation: CFRunLoopRef

NSRunLoopCFRunLoopRef都代表Runloop對(duì)象,NSRunLoop是基于CFRunLoopRef的一層OC包裝,CFRunLoopRef開源的

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

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

Runloop與線程

為什么聊Runloop一定要搭上線程?我們知道,程序里的每一句代碼,都會(huì)在線程(主線程/子線程)里面被執(zhí)行,上面四種獲得Runloop對(duì)象的代碼也不例外,一定是跑在線程里面的。之前我們說到,Runloop是為了讓程序不退出,其實(shí)更準(zhǔn)確地說,是為了保持某個(gè)線程不結(jié)束,只要還有未結(jié)束的線程,那么整個(gè)程序就不會(huì)退出,因?yàn)榫€程是程序的運(yùn)行的調(diào)度的基本單元。

線程與Runloop的關(guān)系是一對(duì)一的,一個(gè)新創(chuàng)建的線程,是沒有Runloop對(duì)象的,當(dāng)我們?cè)谠摼€程里第一次通過上面的API獲得Runloop時(shí),Runloop對(duì)象才會(huì)被創(chuàng)建,并且通過一個(gè)全局字典將Runloop對(duì)象和該線程存儲(chǔ)綁定在一起,形成一對(duì)一關(guān)系。

Runloop會(huì)在線程結(jié)束時(shí)銷毀,主線程的Runloop已經(jīng)自動(dòng)獲取過(創(chuàng)建),子線程默認(rèn)沒有開啟RunLoop(直到你在該線程獲取它)。RunLoop對(duì)象創(chuàng)建后,會(huì)被保存在一個(gè)全局的Dictionary里,線程作為key,Runloop對(duì)象作為value。

關(guān)于Runloop的創(chuàng)建和保存,我們還可以在CF源碼里面詳細(xì)看看,Runloop的信息是寫在CF源碼文件夾CFRunLoop.c文件里面,我們可以在里面搜索到CFRunLoopGetCurrent()函數(shù)的實(shí)現(xiàn)

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

CFRunLoopGetCurrent()中又是通過_CFRunLoopGet0來獲得Runloop對(duì)象的

Runloop的獲取創(chuàng)建流程

Runloop對(duì)象底層結(jié)構(gòu)

我們可以在源碼CFRunloop.c中找到Runloop的定義


**************????????__CFRunLoop????????***********
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
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
    uint32_t _winthread;

 //??????????????核心組成????????????
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
 //??????????????核心組成????????????

    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};


**************????????__CFRunLoopMode????????***********
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    Boolean _stopped;
    char _padding[3];

    //??????????????核心組成????????????
    CFStringRef _name;
    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 */
};

我們需要掌握與Runloop相關(guān)的5個(gè)相關(guān)的類

  • CFRunLoopRef——這個(gè)就是Runloop對(duì)象
  • CFRunLoopModeRef——其內(nèi)部主要包括四個(gè)容器,分別用來存放source0、source1observer以及timer
  • CFRunLoopSourceRef——分為source0source1
    source0:包括 觸摸事件處理、[performSelector: onThread: ]
    source1:包括 基于Port的線程間通信、系統(tǒng)事件捕捉
  • CFRunLoopTimerRef——timer事件,包括我們?cè)O(shè)置的定時(shí)器事件、[performSelector: withObject: afterDelay:]
  • CFRunLoopObserverRef——監(jiān)聽者,Runloop狀態(tài)變更的時(shí),會(huì)通知監(jiān)聽者進(jìn)行函數(shù)回調(diào),UI界面的刷新就是在監(jiān)聽到Runloop狀態(tài)為BeforeWaiting時(shí)進(jìn)行的。

對(duì)于以上這幾個(gè)類相互之間的關(guān)系,可以通過如下的圖來描繪

從圖中可看出,一個(gè)RunLoop對(duì)象里面包含了若干個(gè)RunLoopModeRunLoop內(nèi)部是通過一個(gè)集合容器_modes來裝這些RunLoopMode的。

RunLoopMode內(nèi)部核心內(nèi)容是4個(gè)數(shù)組容器,分別用來裝source0,source1,observertimerRunLoop對(duì)象內(nèi)部有一個(gè)_currentMode,它指向了該RunLoop對(duì)象的其中一個(gè)RunLoopMode,它代表的含義是RunLoop當(dāng)前所運(yùn)行的RunLoopMode,所謂“運(yùn)行”也就是說,RunLoop當(dāng)前只會(huì)執(zhí)行_currentMode所指向的RunLoopMode里面所包括的事件(source0、source1、observer、timer

另外,RunLoop對(duì)象內(nèi)部還有另外兩個(gè)成員 _commonModes_commonModeItems,這個(gè)稍后解釋。

還有就是RunLoop對(duì)象內(nèi)部還包括一個(gè)線程對(duì)象_pthread,這就是跟它一一對(duì)應(yīng)的那個(gè)線程對(duì)象。

source0

上面介紹了包括觸摸事件處理、[performSelector: onThread: ],這個(gè)也可以通過代碼來驗(yàn)證一下。首先看一下觸摸事件,在ViewController里面重寫方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"點(diǎn)擊屏幕");
}

加上斷點(diǎn),然后運(yùn)行程序,點(diǎn)擊屏幕,此時(shí)程序會(huì)停在


有時(shí)我們無法在Xcode的debug navigator看到完整的函數(shù)調(diào)用棧
這時(shí)可以通過LLDB指令bt,在控制臺(tái)打印出完整的函數(shù)調(diào)用棧信息
可以看出系統(tǒng)是通過一個(gè)CF的函數(shù)__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__來調(diào)用UIKit進(jìn)行事件處理的,上面這個(gè)名字老長(zhǎng)的函數(shù),從命名就看的很清楚了,Runloop現(xiàn)在處理的是一個(gè)source0。
通過同樣的方法,可以證明[performSelector: onThread: ]生成的也是一個(gè)source0。

- (void)viewDidLoad {
    [super viewDidLoad];
//創(chuàng)建線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
    //啟動(dòng)線程
    [thread start];
    //向線程加入perform事件
    [self performSelector:@selector(performEvent) onThread:thread withObject:nil waitUntilDone:YES];
//  下面這個(gè)方法同樣產(chǎn)生source0
//  [self performSelector:@selector(performEvent) onThread:thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
}


-(void)runThread {
    //確保線程常駐
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

- (void)performEvent {
    NSLog(@"處理Perform事件");
}
[performSelector: onThread: ]生成source0

source1

上面介紹了source1包括系統(tǒng)事件捕捉和基于port的線程間通信。什么是系統(tǒng)事件捕捉?又如何理解基于port的線程間通信?其實(shí),我們手指點(diǎn)擊屏幕,首先產(chǎn)生的是一個(gè)系統(tǒng)事件,通過source1來接受捕捉,然后由Springboard程序包裝成source0分發(fā)給應(yīng)用去處理,因此我們?cè)贏pp內(nèi)部接受到觸摸事件,就是source0,這一前一后的關(guān)系要理清楚。

基于port的線程間通信通過下面的圖示大致理解即可

image.png

CFRunLoopTimerRef

同樣,可以在Xcode里面通過LLDBbt指令,查看NSTimer事件和[performSelector: withObject: afterDelay]事件的函數(shù)調(diào)用棧,發(fā)現(xiàn)它們都是通過
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__函數(shù)被吊起的。從函數(shù)名看出,它們確實(shí)是屬于timer事件(CFRunLoopTimerRef

CFRunLoopObserverRef

我們知道 observer 是用來監(jiān)聽Runloop狀態(tài)的。還可以處理UI界面刷新,那我們些的那些UI界面相關(guān)的控制代碼,是怎么被執(zhí)行的呢?圖示如下


Runloop狀態(tài)總共有以下幾種

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),//進(jìn)入runloop循環(huán)
    kCFRunLoopBeforeTimers = (1UL << 1),//即將處理timer事件
    kCFRunLoopBeforeSources = (1UL << 2),//即將處理source事件
    kCFRunLoopBeforeWaiting = (1UL << 5),//即將進(jìn)入休眠(等待消息喚醒)
    kCFRunLoopAfterWaiting = (1UL << 6),//休眠結(jié)束(被消息喚醒)
    kCFRunLoopExit = (1UL << 7),//退出runloop循環(huán)
    kCFRunLoopAllActivities = 0x0FFFFFFFU//集合以上所有的狀態(tài)
};

想要在調(diào)試中看到Runloop的狀態(tài)變化,可以通過Runloop的api添加observer,具體如下

//創(chuàng)建observer
    //通過block創(chuàng)建
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        //observer回調(diào)處理
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"kCFRunLoopEntry");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"kCFRunLoopBeforeTimers");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"kCFRunLoopBeforeSources");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"kCFRunLoopBeforeWaiting");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"kCFRunLoopAfterWaiting");
                break;
            case kCFRunLoopExit:
                NSLog(@"kCFRunLoopExit");
                break;

            default:
                break;
        }
    })

    //添加observer到runloop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    //釋放observer
    CFRelease(observer);

程序運(yùn)行之后,你會(huì)在控制臺(tái)看到不斷的有如下打印

可以看出,Runloop的狀態(tài)切換時(shí),都會(huì)被observer監(jiān)聽到。

_modes和_commonModes

你會(huì)好奇,RunLoop內(nèi)部要這么多RunLoopMode干什么,為什么不把事件都放在一個(gè)Mode里面就好,現(xiàn)在用一個(gè)實(shí)際案例來解釋這個(gè)問題。

首先,我們?cè)谝粋€(gè)iOS工程里面,在界面上添加一個(gè)UITextView


然后在ViewController里面開啟一個(gè)可循環(huán)的定時(shí)器

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerEvent) userInfo:nil repeats:YES];
}

- (void)timerEvent {
    NSLog(@"處理Timer事件");
}

@end

運(yùn)行程序之后,控制臺(tái)回每隔1秒調(diào)用一次timerEvent方法執(zhí)行


系統(tǒng)是怎么辦到的呢,其實(shí),[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerEvent) userInfo:nil repeats:YES];內(nèi)部,就是每隔一秒鐘,往當(dāng)前線程(主線程)的RunLoop對(duì)象內(nèi)部的其中一個(gè)Mode添加timer事件,并放在它的timer容器里面,
然后在RunLoop的不斷循環(huán)中,被依次處理。所謂處理timer事件,就是去調(diào)用timer所綁定的OC方法,或者block。

當(dāng)時(shí)滑動(dòng)界面上我們剛才添加的那個(gè)UITextView時(shí),你會(huì)發(fā)現(xiàn)控制臺(tái)里面timerEvent的方法停住了,為啥呢?這個(gè)問題經(jīng)常在iOS面試時(shí)碰到,相信你也知道答案。剛才我們介紹RunLoop內(nèi)部結(jié)構(gòu)的時(shí)候了解到,其內(nèi)部有若干個(gè)RunLoopMode,其中有兩個(gè)我們需要掌握,它們名字分別是

  • kCFRunLoopDefaultMode
    App的默認(rèn)Mode,通常主線程時(shí)在這個(gè)Mode下運(yùn)行的
  • UITrakingRunLoopMode
    界面追蹤Mode,顧名思義,App有如果有Scrollview的觸摸滑動(dòng)事件,會(huì)放到該Mode的事件容器里,所以當(dāng)用戶通過屏幕操作界面上的ScrollView時(shí),App會(huì)切換到該Mode下運(yùn)行,處理當(dāng)前的滑動(dòng)事件。

上面我們通過通過scheduledTimerWithTimeInterval方法增加的timer事件,實(shí)際上是被系統(tǒng)默認(rèn)放到了主線程RunLoop的kCFRunLoopDefaultMode內(nèi),當(dāng)我們不滑動(dòng)屏幕時(shí),主線程跑在這個(gè)Mode下,所以可以處理我們添加的timer事件。

當(dāng)我們手指滑動(dòng)屏幕的時(shí)候,主線程會(huì)被切換到UITrakingRunLoopMode下去運(yùn)行,因此就處理不了我們的timer事件了,因?yàn)?code>timer事件壓根就沒有添加到前線程運(yùn)行的Mode里面。

這樣劃分開的好處就是,可以把不同優(yōu)先級(jí)的事件,分開放置,互不干擾。因?yàn)樘O果的最高理念是用戶體驗(yàn)至上,當(dāng)用戶在滑動(dòng)操作界面的時(shí)候,蘋果認(rèn)為最好的體驗(yàn)是盡可能讓用戶感覺不到卡頓,如果把耗時(shí)較大的事件和滑動(dòng)事件放在同一個(gè)Mode里面同時(shí)去處理,就有可能造成界面卡頓。擁有多個(gè)Mode,就能將事件分開處理,保證用戶體驗(yàn)。UITrakingRunLoopMode這個(gè)名子也就是提醒開發(fā)人員,不要把不相關(guān)的耗時(shí)操作事件添加到這個(gè)Mode里面,以免影響用戶體驗(yàn)。

要解決上面的案例中的問題,讓滑動(dòng)界面的同時(shí),還可以處理timer事件,就需要利用NSTimer的另外一個(gè)方法來添加計(jì)時(shí)器

- (void)viewDidLoad {
    [super viewDidLoad];
//創(chuàng)建一個(gè)timer
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"timer事件2");
    }];
//將timer添加到RunLoop的指定模式里面
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

代碼中,我將創(chuàng)建的timer添加到了NSRunLoopCommonModes中,這個(gè)NSRunLoopCommonModes其實(shí)不是一個(gè)具體的模式,它可以理解成一個(gè)標(biāo)簽,被打上這種標(biāo)簽的具體Mode會(huì)被放入到RunLoop內(nèi)部的一個(gè)容器成員_commonModes里面,它是一個(gè)CFMutableSetRef,默認(rèn)情況下,_commonModes內(nèi)部裝著kCFRunLoopDefaultMode + UITrakingRunLoopMode這兩個(gè)Mode,等于說這兩個(gè)Mode是具有NSRunLoopCommonModes標(biāo)記的,因此都被添加進(jìn)了_commonModes,根據(jù)上面的代碼,timer將不會(huì)被添加到某個(gè)具體的Mode里,而是會(huì)被放入RunLoop_commonModeItems這個(gè)容器里。只要App運(yùn)行在_commonModes所包含的某個(gè)Mode下,就會(huì)去處理_commonModeItems里面的事件。當(dāng)然,所運(yùn)行的那個(gè)Mode自己本身所包含的事件也是會(huì)被處理的,這點(diǎn)不要忽略。以上,就是解決timer失效問題的方法和底層的原理。

從源碼梳理Runloop的運(yùn)行流程

上面我們討論Runloop內(nèi)部的循環(huán)在運(yùn)行過程中,被分成了若干個(gè)狀態(tài),那么這些狀態(tài)之間是按如何順序切換的呢,Runloop內(nèi)部的執(zhí)行邏輯到底如何呢,這就需要通過源碼來一窺究竟了。RunLoop的源文件CFRunLoop.c是比較復(fù)雜的,而且是純C實(shí)現(xiàn)的,大家看的時(shí)候難免會(huì)不太習(xí)慣,而且這里面有很多函數(shù),那個(gè)才是Runloop的入口函數(shù)呢。其實(shí)我們?cè)谏厦孀C明 觸摸事件屬于source0的時(shí)候,就可以從函數(shù)調(diào)用棧里面找到答案


很明顯,在通過觸摸事件觸發(fā)的函數(shù)調(diào)用棧里面,CF框架最初是通過CFRunLoopRunSpecific函數(shù)進(jìn)入Runloop的,接下來便調(diào)用了__CFRunLoopRun,從名字就能看出這里可定是入口了。我們來看一下這兩個(gè)函數(shù),由于這兩個(gè)函數(shù)都比較復(fù)雜,為了便于理解Runloop的執(zhí)行邏輯,代碼經(jīng)過精簡(jiǎn),保留核心步驟代碼,并且標(biāo)記為①~?個(gè)主要步驟,展示如下

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    //????????***①***????????通知observer----------kCFRunLoopEntry
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    //????????????????啟動(dòng)runloop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    //????????***?***????????通知observer----------kCFRunLoopEntry
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
 
    return result;
}





static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

    //??????退出do-while循環(huán)的標(biāo)簽retVal
    int32_t retVal = 0;

    //??????runloop的核心就是這樣一個(gè)do-while循環(huán)
    do {
        
      //????????***②***????????通知observer-----kCFRunLoopBeforeTimers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);


      //????????***③***????????通知observer-----kCFRunLoopBeforeSources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);


      //????????***④***????????處理Blocks
       __CFRunLoopDoBlocks(rl, rlm);


      //????????***⑤***????????處理source0-------
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

        if (sourceHandledThisLoop) {
            //????????????????需要的話處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
    }


      //????????***⑥***????????判斷有沒有source1

        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {

            //????????????????如果有source1,跳轉(zhuǎn)到標(biāo)簽handle_msg處
            goto handle_msg;
        }


      //????????***⑦***????????通知observer-----kCFRunLoopBeforeWaiting
       __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

      //開始休眠
      __CFRunLoopSetSleeping(rl);

      //等待別的消息來喚醒當(dāng)前線程    
      __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
      //線程喚醒
      __CFRunLoopUnsetSleeping(rl);


     //????????  ⑧  ????????通知observer-----kCFRunLoopAfterWaiting 結(jié)束休眠
      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

//??????
handle_msg://????????***⑨***????????處理喚醒事件


        //??????????被timer喚醒
        if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
        //????????????????處理timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        }
        //??????????被GCD喚醒
        else if (livePort == dispatchPort) {
        //????????????????處理GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            
        } 
        //??????????source1喚醒
        else {
        //????????????????處理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        
        }


      //????????***⑩***????????處理Blocks
       __CFRunLoopDoBlocks(rl, rlm);
        
      //????????***?***????????設(shè)置返回值retVal
      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;
}

將上面的執(zhí)行流程總結(jié)圖示如下
Runloop運(yùn)行流程圖

以下是RunLoop中的7個(gè)核心操作單元

  • __CFRunLoopDoSource1:處理source1事件,其內(nèi)部調(diào)用了
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • __CFRunLoopDoSources0:處理source0事件,其內(nèi)部調(diào)用了
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • __CFRunLoopDoObservers:通知觀察者,其內(nèi)部調(diào)用了
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
  • __CFRunLoopDoTimers:處理定時(shí)器事件,其內(nèi)部調(diào)用了
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • __CFRunLoopDoBlocks:處理blocks,其內(nèi)部調(diào)用了
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__:處理GCD異步主線程任務(wù)
  • __CFRunLoopServiceMachPort休眠線程,等待消息喚醒

注意點(diǎn)一 ——GCD與RunLoop
GCD和RunLoop是兩個(gè)獨(dú)立的機(jī)制,大部分情況下是彼此不相關(guān)的。但是上面我們看到RunLoop里面有一個(gè)核心操作叫__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__,翻譯過來大概是 RunLoop正在服務(wù)(GCD的)主線程隊(duì)列,說明GCD講一些事情交給了RunLoop處理。實(shí)際上,當(dāng)我們從子線程異步調(diào)回到主線程執(zhí)行任務(wù)時(shí),GCD會(huì)將這個(gè)主線程任務(wù)丟給RunLoop,最后通過__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__函數(shù)傳送給GCD內(nèi)部去處理,下面的代碼就是這種情況

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   NSLog(@"點(diǎn)擊屏幕");
   
   dispatch_async(dispatch_get_global_queue(0, 0), ^{
       NSLog(@"子線程事件");
       dispatch_async(dispatch_get_main_queue(), ^{
           NSLog(@"回到主線程");
           
       });
   });
}

函數(shù)調(diào)用如下
RunLoop處理GCD任務(wù)

注意點(diǎn)二—— 線程的休眠細(xì)節(jié)
之前我們說過RunLoop可以幫助程序節(jié)省CPU資源,提高性能,有事情做做事,沒事情做就休眠休息,而正是__CFRunLoopServiceMachPort幫助我們實(shí)現(xiàn)了這個(gè)休眠功能。這個(gè)函數(shù)的作用,就是阻塞線程,讓線程真正停下來,不在繼續(xù)往下執(zhí)行,等待被喚醒。那么這個(gè)阻塞是如何實(shí)現(xiàn)的呢?

為了不在繼續(xù)執(zhí)行下面的代碼,你可能會(huì)想到用一個(gè)無限循環(huán) while(1){;},這樣其后面的代碼部分就都不會(huì)執(zhí)行,但這并不是真正的休眠,只不過程序走到while(1){;}這個(gè)死循環(huán)里面出不來了,但是線程并沒有真正停下來,while(1){;}所編譯成的那幾句匯編指令正在不停的被CPU反復(fù)的執(zhí)行,所以仍然需要占用CPU資源。

__CFRunLoopServiceMachPort函數(shù)是一種真正意義上的休眠,它使得當(dāng)前線程真正停下來,并且不再需要占用CPU資源去執(zhí)行匯編指令了。其內(nèi)部其實(shí)調(diào)用了mach_msg()函數(shù),這是系統(tǒng)內(nèi)核提供給我們的一個(gè)API,它使的我們作為應(yīng)用層面的開發(fā)人員,可以調(diào)用內(nèi)核層面的函數(shù),線程休眠就是一種內(nèi)核層面的操作。

到此RunLoop的內(nèi)部結(jié)構(gòu)以及運(yù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)容

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