iOS 中有2套 API 可以通路和使用 RunLoop。分别是
- Foundation:NSRunLoop
- CoreFoundation:CFRunLoopRef
//Foundation
[NSRunLoop currentRunLoop]; // 獲得目前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
//Core Foundation
CFRunLoopGetCurrent(); // 獲得目前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
NSRunLoop 是對 CFRunLoopRef 的一層 OC 包裝,是以要了解 RunLoop 的内部結果,就需要了解 CFRunLoopRef。
- 每條線程都有與之一一對應的 RunLoop 對象
- 主線程的 RunLoop 已經自動建立好了,子線程的 RunLoop 需要主動建立
- RunLoop 在第一次擷取時建立,線上程結束時消失
RunLoop 相關的5個類
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopModeRef 代表 RunLoop 的運作模式
- 一個 RunLoop 包含若幹個 Mode,每個 Mode 包含若幹個 Source/Timer/Observer
- 每次 RunLoop 啟動,隻能指定一個 Mode,這個 Mode 被叫做 CurrentMode
- 如果需要切換 Mode,隻能退出 RunLoop,則以一個 Mode 進入
- 這樣做的目的是為了分隔開不同組的 Source/Timer/Observer 互不影響
系統預設注冊了5個Mode
- kCFRunLoopDefaultMode:App 的預設 Mode,通常主線程是在這個 Mode 下運作
- UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
- UIInitializationRunLoopMode: 在剛啟動 App 時進入的第一個 Mode,啟動完成後就不再使用
- GSEventReceiveRunLoopMode: 接受系統事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
CFRunLoopSourceRef 事件源(輸入源)
- 早期的分法:
- Ported-Based Source
- Custom Input Source
- Cocoa Perform Selector Source
- 現在的分法
- Source0:非基于 port 的,使用者主動觸發的事件
- Source1: 基于 port的,通過核心線上程間互相發送消息
CFRunLoopTimerRef 是基于時間的觸發器
- 基本上說就是 NSTimer,它會收到 RunLoopMode 的影響
- GCD 的 timer 不受 RunLoopMode 的影響
- CFRunLoopObserverRef 觀察者,監聽 RunLoop 狀态的變化
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将進入 RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将處理 NSTimer
kCFRunLoopBeforeSources = (1UL << 2), // 即将處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 退出 RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
添加 Observer
//1、獲得目前線程下的 RunLoop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//2、為 RunLoop 建立觀察者
CFRunLoopObserverRef obersver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
//3、為目前的 RunLoop 添加觀察者
CFRunLoopAddObserver(runloop, obersver, kCFRunLoopDefaultMode);
//4、在 CoreFoundation 架構中, create、copy、retain 過的對象都必須在最後 release
CFRelease(obersver);
NSTimer 經常會不準确,原因是什麼?
NSTimer 在建立的時候經常會指派到特定的 NSRunLoopMode 中去,舉個例子,預設建立的NSTimer 是被添加到 NSRunLoopDefaultMode 中去,當你的頁面上有 UIScrollView 或者子類的時如果被拖動了,目前 RunLoop 的 NSRunloopMode 會從 NSDefaultRunLoopMode 轉變為 UITrackingRunLoopMode 。遇到這種情況你需要精确的 NSTimer 的話,在建立好 NSTimer 之後,設定 RunLoopMod 為 NSRunLoopCommonModes
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSTimer 會受 NSRunLoopMode 影響,GCD 的 timer 則不會。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
/*
隻在預設狀态下執行的 NSTimer
[NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"我在執行了");
}];
*/
/*
指定 NSRunLoopMode 的 NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
*/
/*
GCD 的機關是 納秒.
使用 GCD 建立的 timer 正常建立後不會執行,因為建立後設定了指定的時間後觸發,是以當代碼運作到最後一行的時候,Timer 還沒執行,就被銷毀了。是以我們必須設定一個屬性去儲存它。
*/
//1、建立隊列
dispatch_queue_t queue = dispatch_get_main_queue();
//2、建立 timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
self.timer = timer;
//3、設定 timer 的參數:精準度、時間間隔
//第三個參數為 GCD timer 的精準度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//4、為 Timer 設定任務
dispatch_source_set_event_handler(timer, ^{
NSLog(@"%@",[NSRunLoop currentRunLoop]);
});
//5、執行任務
dispatch_resume(timer);
}
- (void)show{
NSLog(@"shw-%@",[NSThread currentThread]);
NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
@end
監聽 RunLoop
//給 RunLoop 添加監聽者
- (void)testRunLoopObserver{
//建立監聽者
// CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopAllActivities, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext *context#>)
/*
建立監聽對象
參數1:配置設定記憶體空間
參數2:要監聽的狀态 kCFRunLoopAllActivities :所有狀态
參數3:是否要持續監聽
參數4:優先級
參數5:回調
*/
CFRunLoopObserverRef oberver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop 閃亮登場");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop 大哥要處理 Timer 了");
break;
case kCFRunLoopBeforeSources:
//Source 有2種。Source0:非基于 port 的,使用者主動觸發的事件。Source1:基于 port,通過核心和其它線程互相發送消息
NSLog(@"RunLoop 大哥要處理 Source 了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop 大哥沒事幹要睡覺了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"");
NSLog(@"RunLoop 大哥終于等到有緣人了,要醒來開始幹活了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop 大哥要退出離開了");
break;
default:
break;
}
});
/*
參數1:要監聽哪個RunLoop
參數2:監聽者
參數3:要監聽 RunLoop 在哪種運作模式下的狀态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), oberver, kCFRunLoopDefaultMode);
//CoreFoundation 的記憶體管理:凡是帶有 Create、Copy、Retain等字眼的函數建立出來的對象都需要在最後調用 release
CFRelease(oberver);
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(wakeupRunLoop) userInfo:nil repeats:YES];
}
//等到 RunLoop 休眠後,5秒鐘叫醒 RunLoop
- (void)wakeupRunLoop{
NSLog(@"%s",__func__);
}
/*
2018-08-01 11:23:49.401626+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:49.401950+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:49.402326+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:49.402509+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:49.402721+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:49.402855+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:49.403080+0800 RunLoop[38148:1994974] RunLoop 大哥沒事幹要睡覺了
2018-08-01 11:23:49.459238+0800 RunLoop[38148:1994974]
2018-08-01 11:23:49.459512+0800 RunLoop[38148:1994974] RunLoop 大哥終于等到有緣人了,要醒來開始幹活了
2018-08-01 11:23:49.459740+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:49.459932+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:49.460431+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:49.460607+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:49.460775+0800 RunLoop[38148:1994974] RunLoop 大哥沒事幹要睡覺了
2018-08-01 11:23:49.880631+0800 RunLoop[38148:1994974]
2018-08-01 11:23:49.880867+0800 RunLoop[38148:1994974] RunLoop 大哥終于等到有緣人了,要醒來開始幹活了
2018-08-01 11:23:49.881530+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:49.881699+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:49.881870+0800 RunLoop[38148:1994974] RunLoop 大哥沒事幹要睡覺了
2018-08-01 11:23:54.402263+0800 RunLoop[38148:1994974]
2018-08-01 11:23:54.402562+0800 RunLoop[38148:1994974] RunLoop 大哥終于等到有緣人了,要醒來開始幹活了
2018-08-01 11:23:54.402773+0800 RunLoop[38148:1994974] -[ViewController wakeupRunLoop]
2018-08-01 11:23:54.403081+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:54.403245+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:54.403476+0800 RunLoop[38148:1994974] RunLoop 大哥沒事幹要睡覺了
2018-08-01 11:23:59.402151+0800 RunLoop[38148:1994974]
2018-08-01 11:23:59.402511+0800 RunLoop[38148:1994974] RunLoop 大哥終于等到有緣人了,要醒來開始幹活了
2018-08-01 11:23:59.402687+0800 RunLoop[38148:1994974] -[ViewController wakeupRunLoop]
2018-08-01 11:23:59.402913+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Timer 了
2018-08-01 11:23:59.403037+0800 RunLoop[38148:1994974] RunLoop 大哥要處理 Source 了
2018-08-01 11:23:59.403156+0800 RunLoop[38148:1994974] RunLoop 大哥沒事幹要睡覺了
*/
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-so7emxLG-1585659764330)(https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/[email protected])]
上個實驗是在主線程對 RunLoop 進行的監聽,但是由于是主線程是由系統建立的,是以系統也建立了對應的主 RunLoop,是以我們看不到 RunLoop 建立的狀态,為了模拟完整的狀态,我們開啟子線程,在子線程中模拟
- (void)testRunLoopObserverOnSubThread{
//建立并發隊列
dispatch_queue_t queue = dispatch_queue_create("com.lbp.testRunLoopOnSubThread", DISPATCH_QUEUE_CONCURRENT);
//開啟子線程
dispatch_async(queue, ^{
//1、獲得目前線程下的 RunLoop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//2、為 RunLoop 建立觀察者
CFRunLoopObserverRef obersver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop 閃亮登場");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop 大哥要處理 Timer 了");
break;
case kCFRunLoopBeforeSources:
//Source 有2種。Source0:非基于 port 的,使用者主動觸發的事件。Source1:基于 port,通過核心和其它線程互相發送消息
NSLog(@"RunLoop 大哥要處理 Source 了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop 大哥沒事幹要睡覺了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"");
NSLog(@"RunLoop 大哥終于等到有緣人了,要醒來開始幹活了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop 大哥要退出離開了");
break;
default:
break;
}
});
//為了運作 RunLoop 必須觸發事件
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(wakeUpRunLoopOnSubThread) userInfo:nil repeats:NO];
//3、為目前的 RunLoop 添加觀察者
CFRunLoopAddObserver(runloop, obersver, kCFRunLoopDefaultMode);
//4、在 CoreFoundation 架構中, create、copy、retain 過的對象都必須在最後 release
CFRelease(obersver);
//5、在非主線程建立的 RunLoop 必須觸發運作
[[NSRunLoop currentRunLoop] run];
});
}
- (void)wakeUpRunLoopOnSubThread{
NSLog(@"%s",__func__);
}
/*
2018-08-01 14:23:06.453282+0800 RunLoop[2376:115968] RunLoop 閃亮登場
2018-08-01 14:23:06.453608+0800 RunLoop[2376:115968] RunLoop 大哥要處理 Timer 了
2018-08-01 14:23:06.453781+0800 RunLoop[2376:115968] RunLoop 大哥要處理 Source 了
2018-08-01 14:23:06.453982+0800 RunLoop[2376:115968] RunLoop 大哥沒事幹要睡覺了
2018-08-01 14:23:08.458237+0800 RunLoop[2376:115968]
2018-08-01 14:23:08.458658+0800 RunLoop[2376:115968] RunLoop 大哥終于等到有緣人了,要醒來開始幹活了
2018-08-01 14:23:08.458894+0800 RunLoop[2376:115968] -[ViewController wakeUpRunLoopOnSubThread]
2018-08-01 14:23:08.459082+0800 RunLoop[2376:115968] RunLoop 大哥要退出離開了
*/
RunLoop 内部運作原理
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-iynhKKgg-1585659764331)(https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/image-20180801113342611.png)]
- 圖上左上角的 Input source 是早期 RunLoop 的分法,現在分法為:Source0 和 Source1。
- Source0:非基于 port 的,使用者主動觸發的事件。
- Source1:基于 port,通過核心和其它線程互相發送消息
- RunLoop 我們不能自己手動建立,而是可以通過 [NSRunLoop currentRunLoop] 方法擷取,類似于懶加載。系統底層的做法是在全局維護了一個字典,字典的 key 和 value 分别是目前的線程和線程對應的 RunLoop,如果新開辟的線程沒有對應的 RunLoop,系統則為其建立 RunLoop,并将其寫入字典(線程、為其建立的 RunLoop)
RunLoopMode 的概念

底層實作
内部就是 do-while 的循環,在這個循環内部不斷處理各種任務(Timer、Source、Observer)
我們來看看蘋果官方開源的 CFRunLoop.c 檔案。看幾個關鍵函數的實作猜測下 RunLoop 的内部原理
以下的代碼都有注釋說明
__CFRunLoopModeIsEmpty
此函數的作用就是判斷這個 Mode 下面有沒有 source0、source1、timer,隻要存在就說明目前 Mode 不是空的,同時看看這個 Mode 是不是屬于目前的 RunLoop
// expects rl and rlm locked
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
CHECK_FOR_FORK();
if (NULL == rlm) return true;
#if DEPLOYMENT_TARGET_WINDOWS
if (0 != rlm->_msgQMask) return false;
#endif
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) return false; // represents the libdispatch main queue
// 判斷時候有沒有_sources0
if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
// 判斷時候有沒有_sources1
if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
// 判斷時候有沒有_timers
if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;
struct _block_item *item = rl->_blocks_head;
while (item) {
struct _block_item *curr = item;
item = item->_next;
Boolean doit = false;
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
} else {
doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
}
if (doit) return false;
}
return true;
}
CFRunLoopRun、CFRunLoopRunInMode
1、2個函數的作用分别是讓 RunLoop 跑在 KCFRunLoopDefaultMode 下和特定的 Mode 下
2、2個函數本質上都是調用 CFRunLoopRunSpecific
// 用DefaultMode啟動
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 用指定的Mode啟動,允許設定RunLoop逾時時間
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
CFRunLoopRunSpecific
參數1: RunLoop 對象。參數2:運作 Mode 名稱。參數3:逾時時間。參數4:主_CFRunLoopRun 會用到
// RunLoop的實作
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl))
return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根據modeName找到對應mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 判斷mode裡沒有source/timer, 沒有直接傳回。
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode)
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry )
// 1. 通知 Observers: RunLoop 即将進入 loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 進入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit )
// 10.通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
__CFRunLoopDoObserver
調用 Observer 回調
聯想給 RunLoop 添加觀察者,監聽 RunLoop 狀态。
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) { /* DOES CALLOUT */
CHECK_FOR_FORK();
CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0;
if (cnt < 1) return;
/* Fire the observers */
STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024) ? cnt : 1);
CFRunLoopObserverRef *collectedObservers = (cnt <= 1024) ? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef));
CFIndex obs_cnt = 0;
//周遊 rlm-> _observers,将元素放到 collectedObservers 數組中
for (CFIndex idx = 0; idx < cnt; idx++) {
CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) {
collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo);
}
}
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
for (CFIndex idx = 0; idx < obs_cnt; idx++) {
CFRunLoopObserverRef rlo = collectedObservers[idx];
__CFRunLoopObserverLock(rlo);
if (__CFIsValid(rlo)) {
Boolean doInvalidate = !__CFRunLoopObserverRepeats(rlo);
__CFRunLoopObserverSetFiring(rlo);
__CFRunLoopObserverUnlock(rlo);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);
if (doInvalidate) {
CFRunLoopObserverInvalidate(rlo);
}
__CFRunLoopObserverUnsetFiring(rlo);
} else {
__CFRunLoopObserverUnlock(rlo);
}
CFRelease(rlo);
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
if (collectedObservers != buffer) free(collectedObservers);
}
__CFRunLoopRun
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
mach_port_name_t dispatchPort = MACH_PORT_NULL;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else {
// 設定RunLoop逾時時間
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
__CFPortSet waitSet = rlm->_portSet;
__CFRunLoopUnsetIgnoreWakeUps(rl);
if (rlm->_observerMask & kCFRunLoopBeforeTimers)
// 2. 通知 Observers: RunLoop 即将觸發 Timer 回調
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources)
// 3. 通知 Observers: RunLoop 即将觸發 Source 回調
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 執行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// 4. RunLoop 觸發 Source0 (非port) 回調
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 執行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 5. 如果有 Source1 (基于port) 處于 ready 狀态,直接處理這個 Source1 然後跳轉去處理消息
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
// 通知 Observers: RunLoop 的線程即将進入休眠(sleep)
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 處理消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
// 9.1 如果一個 Timer 到時間了,觸發這個Timer的回調
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
// 9.2 如果有dispatch到main_queue的block,執行block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
/**/
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
}
// 9.3 如果一個 Source1 (基于port) 發出事件了,處理這個事件
else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
/**/
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
// 執行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
// 進入loop時參數說處理完事件就傳回
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)) {
// source/timer一個都沒有
retVal = kCFRunLoopRunFinished;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
// 如果沒逾時,mode裡沒空,loop也沒被停止,那繼續loop
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}