什么是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)述
-
Runloop的
do-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)讓程序停在了Runloop的
do-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
NSRunLoop和CFRunLoopRef都代表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對(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、source1、observer以及timer -
CFRunLoopSourceRef——分為
source0和source1
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)行的。

從圖中可看出,一個(gè)RunLoop對(duì)象里面包含了若干個(gè)RunLoopMode,RunLoop內(nèi)部是通過一個(gè)集合容器_modes來裝這些RunLoopMode的。
RunLoopMode內(nèi)部核心內(nèi)容是4個(gè)數(shù)組容器,分別用來裝source0,source1,observer和timer,RunLoop對(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)用棧
bt,在控制臺(tái)打印出完整的函數(shù)調(diào)用棧信息
__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事件");
}

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的線程間通信通過下面的圖示大致理解即可

CFRunLoopTimerRef
同樣,可以在Xcode里面通過LLDB的bt指令,查看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)看到不斷的有如下打印

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容器里面,
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中的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)部去處理,下面的代碼就是這種情況函數(shù)調(diào)用如下- (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(@"回到主線程"); }); }); }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)行原理就梳理完畢
