RunLoop 本質(zhì):
RunLoop 本質(zhì)上是一個運行循環(huán)撵溃,其作用是保持線程的生命,防止線程被銷毀掂咒,日常開發(fā)中無處不在才沧,為了感受到 RunLoop 的存在,舉個簡單的例子绍刮,我們都知道温圆,APP程序的入口在main.m里面:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
接下來我們試著感受一下 RunLoop,點擊查看 UIApplicationMain孩革,其返回值是一個 int 值:
// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no
// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.
UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nullable argv[_Nonnull], NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
我們在 main 函數(shù)里面用一個 int 變量去接收它岁歉,然后在返回值前后添加輸出語句:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"1 %@", [NSThread currentThread]);
int a = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"2 %@", [NSThread currentThread]);
return a;
}
}
運行程序,看一下輸出:
我們看到膝蜈,只執(zhí)行了第一個輸出锅移,并且當(dāng)前線程是主線程,第二個輸出語句并沒有執(zhí)行饱搏,沒有執(zhí)行的原因非剃,有兩種可能,第一種:UIApplicationMain 內(nèi)部阻塞推沸;第二種:UIApplicationMain 函數(shù)內(nèi)部開啟了一個死循環(huán)努潘;但是一旦程序發(fā)生阻塞,APP 就無法使用了坤学,那么說得通的可能性就是第二種疯坤,這個函數(shù)內(nèi)部開啟了死循環(huán),就是 RunLoop 循環(huán)深浮,它的作用压怠,就是保持主線程的生命,并且?guī)臀覀儽O(jiān)聽一些事件飞苇,比如觸摸菌瘫、時鐘、網(wǎng)絡(luò)等布卡;
Runloop 的兩種形式:
OC: Foundation ——> NSRunLoop
// NSRunLoop獲取RunLoop
[NSRunloop currentRunloop]; // 獲得當(dāng)前線程的RunLoop的方法
[NSRunloop mainRunloop]; // 獲得主線程的RunLoop的方法
C: CoreFoudation ——> CFRunLoopRef
// CFRunLoopRef獲取RunLoop
CFRunloopGetCurrent(); // 獲得當(dāng)前線程的RunLoop的方法
CFRunloopGetMain(); // 獲得主線程的RunLoop的方法
NSRunLoop 是對 CFRunLoopRef 的進(jìn)一步封裝雨让,并且 CFRunLoopRef 是線程安全的,而這一點 NSRunLoop 并不能保證忿等。
RunLoop 與線程的關(guān)系:
每一條線程都有一個與之對應(yīng)的 RunLoop 對象栖忠;
RunLoop 保存在全局的字典內(nèi),是以線程為 key,RunLoop 為 value庵寞;
RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(懶加載狸相, 不獲取是不會創(chuàng)建的),RunLoop 的銷毀是發(fā)生在線程結(jié)束時捐川,只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)脓鹃。
RunLoop 對外的接口:
在 CoreFoundation 里面關(guān)于 RunLoop 有5個類:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef; // CFRunLoopRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef; // CFRunLoopSourceRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef; // CFRunLoopObserverRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef; // CFRunLoopTimerRef
CFRunLoopModeRef
其中 CFRunLoopModeRef 類并沒有對外暴露,只是通過 CFRunLoopRef 的接口進(jìn)行了封裝:
一個 RunLoop 包含若干個 Mode古沥,每個 Mode 又包含若干個 Source / Timer / Observer瘸右,每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode岩齿,這個 Mode 被稱作 CurrentMode尊浓,如果需要切換 Mode,只能退出 Loop纯衍,再重新指定一個 Mode 進(jìn)入栋齿。這樣做主要是為了分隔開不同組的 Source / Timer / Observer,讓其互不影響襟诸。
CFRunLoopSourceRef 是事件產(chǎn)生的地方瓦堵。Source有兩個版本:Source0 和 Source1。
? Source0 只包含了一個回調(diào)(函數(shù)指針)歌亲,它并不能主動觸發(fā)事件菇用。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source)陷揪,將這個 Source 標(biāo)記為待處理惋鸥,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件悍缠。
? Source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針)卦绣,被用于通過內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動喚醒 RunLoop 的線程飞蚓,其原理在下面會講到滤港。
CFRunLoopTimerRef 是基于時間的觸發(fā)器,它和 NSTimer 是 toll-free bridged 的趴拧,可以混用溅漾。其包含一個時間長度和一個回調(diào)(函數(shù)指針)。當(dāng)其加入到 RunLoop 時著榴,RunLoop會注冊對應(yīng)的時間點呈宇,當(dāng)時間點到時腺晾,RunLoop 會被喚醒以執(zhí)行那個回調(diào)拓提。
CFRunLoopObserverRef 是觀察者斥扛,每個 Observer 都包含了一個回調(diào)(函數(shù)指針)享完,當(dāng) RunLoop 的狀態(tài)發(fā)生變化時,觀察者就能通過回調(diào)接受到這個變化,RunLoop 的狀態(tài)如下:
/* Run Loop Observer Activities */
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
};
上面的 Source / Timer / Observer 被統(tǒng)稱為 mode item,一個 item 可以被同時加入多個 Mode瞎饲。但一個 item 被重復(fù)加入同一個 Mode 時是不會有效果的口叙。如果一個 Mode 中一個 item 都沒有炼绘,則 RunLoop 會直接退出,不進(jìn)入循環(huán)妄田。
RunLoop 的 Mode:
CFRunLoopMode 和 CFRunLoop 的結(jié)構(gòu)大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
常見的2種 Mode:
kCFRunloopDefaultMode //程序的默認(rèn)Mode
UITrackingRunloopMode //界面追蹤Mode
不同 Mode 對應(yīng)的事件類型都不相同俺亮,例如我們平時使用的 Timer:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
// timerWithTimeInterval:target:selector:userInfo:repeats: 創(chuàng)建的 Timer 需要手動添加到RunLoop中才能運行
// 兩個 Mode 同時添加,在兩個模式下都會運行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 默認(rèn)模式下運行疟呐,但當(dāng)觸摸事件發(fā)生時脚曾,就會停止
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; // 有觸摸事件時才會運行,但是時間結(jié)束后就會停止
// 添加到 NSRunLoopCommonModes 中启具,在兩個模式下都會運行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)timerMethod {
NSLog(@"%@", [NSThread currentThread]);
}
通過這種方式創(chuàng)建的 Timer 本身是不會運行的本讥,只有將 Timer 添加到 RunLoop 中才會運行,我們先將 Mode 設(shè)置為 NSDefaultRunLoopMode鲁冯,Timer 會運行拷沸,并且打印輸出, 這個時候我們往界面中添加交互控件薯演,例如添加一個ScrollView撞芍,當(dāng)我們滑動這個控件時, Timer 就會停止跨扮,響應(yīng)完觸摸事件又會恢復(fù)運行序无,這是因為 RunLoop 會優(yōu)先響應(yīng) UITrackingRunloopMode 的事件,相反衡创,如果我們將 Mode 設(shè)置為 UITrackingRunloopMode帝嗡,那正常情況下 Timer 不會運行,當(dāng)有 UI 事件的時候璃氢,才會運行丈探;這和我們的初衷相背離,我們希望在任何情況下拔莱,Timer 都能運行碗降,這個時候,我們就要將 Timer 同時添加到 NSDefaultRunLoopMod 和 UITrackingRunloopMode 兩個模式下塘秦,NSRunLoopCommonModes 就是為我們解決這個問題讼渊。
注:
kCFRunloopCommonModes 默認(rèn)包括了 kCFRunloopDefaultMode 和 UITrackingRunloopMode;
kCFRunloopCommonModes 并不是一個真正的模式,只是一個標(biāo)記尊剔,而 kCFRunloopDefaultMode 和 UITrackingRunloopMode 的模式是真正意義的模式爪幻,timer 在設(shè)置了 kCFRunloopCommonModes 標(biāo)記的模式下都能運行,而 kCFRunloopDefaultMode 和 UITrackingRunloopMode 這兩個模式都是 kCFRunloopCommonModes 標(biāo)記的。
RunLoop 運行邏輯:
// 用DefaultMode啟動
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動挨稿,允許設(shè)置RunLoop超時時間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的實現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根據(jù)modeName找到對應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒有source/timer/observer, 直接返回仇轻。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù)奶甘,進(jìn)入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)篷店。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)臭家。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài)疲陕,直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)钉赁。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息蹄殃。線程將進(jìn)入休眠, 直到被下面某一個事件喚醒。
/// ? 一個基于 port 的Source 的事件你踩。
/// ? 一個 Timer 到時間了
/// ? RunLoop 自身的超時時間到了
/// ? 被其他什么調(diào)用者手動喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了诅岩。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,處理消息带膜。
handle_msg:
/// 9.1 如果一個 Timer 到時間了吩谦,觸發(fā)這個Timer的回調(diào)。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block钱慢,執(zhí)行block逮京。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一個 Source1 (基于port) 發(fā)出事件了,處理這個事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進(jìn)入loop時參數(shù)說處理完事件就返回束莫。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入?yún)?shù)標(biāo)記的超時時間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調(diào)用者強(qiáng)制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時懒棉,mode里沒空,loop也沒被停止览绿,那繼續(xù)loop策严。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
可以看到饿敲,實際上 RunLoop 就是這樣一個函數(shù)妻导,其內(nèi)部是一個 do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時怀各,線程就會一直停留在這個循環(huán)里倔韭,直到超時或被手動停止,該函數(shù)才會返回瓢对。