一鸣剪、runloop 簡(jiǎn)介
RunLoop
是通過(guò)內(nèi)部維護(hù)的 事件循環(huán)(Event Loop
) 來(lái)對(duì) 事件/消息 進(jìn)行管理的一個(gè)對(duì)象组底。
- 沒(méi)有消息處理時(shí),休眠已避免資源占用筐骇,由用戶態(tài)切換到內(nèi)核態(tài)债鸡。
- 有消息需要處理時(shí),立刻被喚醒铛纬,由內(nèi)核態(tài)切換到用戶態(tài)厌均。
runloop
的官方文檔在thread
篇章Run Loops,也就從側(cè)面說(shuō)明了runloop
是與線程息息相關(guān)的饺鹃。
1.1 runloop 輸入源與處理機(jī)制
官方有如下一張圖:
線程的輸入源:
-
Port Source
:基于端口的輸入源。 -
Custom Source
:自定義輸入源 -
performSelector
:Cocoa
執(zhí)行Selector
的源间雀。 -
Timer Source
:定時(shí)源悔详。
線程針對(duì)輸入源的處理機(jī)制:
-
handlePort
:處理基于端口的輸入源。 -
customSrc
:處理用戶自定義輸入源惹挟。 -
mySelector
:處理Selector
的源茄螃。 -
timerFired
:處理定時(shí)源。
有以下案例:
- (void)sourcesTest {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationAction:) name:@"notificationTest" object:nil];
//__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer action");
}];
[self performSelector:@selector(performSelectorAction) withObject:nil afterDelay:1.0];
//
// __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
// __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
void (^block)(void) = ^{
NSLog(@"block action");
};
block();
// __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main queue");
});
}
//__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- (void)performSelectorAction {
NSLog(@"timer action");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
NSLog(@"touches action");
[[NSNotificationCenter defaultCenter] postNotificationName:@"notificationTest" object:nil];
}
- (void)notificationAction:(NSNotification *)noti {
// __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__
NSLog(@"notification action");
}
timer
與performSelector
對(duì)應(yīng)的回調(diào)都是__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
:
后續(xù)的調(diào)用函數(shù)不同连锯。
block
對(duì)應(yīng)__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
:
不過(guò)后續(xù)調(diào)用到了
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
归苍。
主線程對(duì)應(yīng)__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
:
系統(tǒng)觸摸事件對(duì)應(yīng)__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
:
通知事件對(duì)應(yīng)__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__
:
小結(jié):
-
調(diào)用
timer
:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
。NSTimer
與performSelector
都屬于timer
运怖。 -
響應(yīng)
source0
:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
拼弃。block
,系統(tǒng)觸摸事件(觸摸事件先交給source1
喚醒runloop
然后交給source0
處理)摇展。 -
響應(yīng)
source1
:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
吻氧。處理系統(tǒng)事件。 -
GCD
主隊(duì)列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
咏连。 -
observer
源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
盯孙。通知。
二祟滴、CFRunloop 的使用
2.1 CFRunLoopMode 驗(yàn)證
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"scrollViewDidScroll: %@",[NSRunLoop currentRunLoop].currentMode);
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSLog(@"scrollViewDidEndDecelerating: %@",[NSRunLoop currentRunLoop].currentMode);
}
- (void)runloopModeTest {
//獲取當(dāng)前runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//獲取當(dāng)前mode
CFRunLoopMode runloopMode = CFRunLoopCopyCurrentMode(runloop);
NSLog(@"runloopMode: %@",runloopMode);
//獲取所有 mode
CFArrayRef modeArray= CFRunLoopCopyAllModes(runloop);
NSLog(@"modeArray: %@",modeArray);
NSTimer *timer = [NSTimer timerWithTimeInterval:3 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timerWithTimeInterval mode: %@",[[NSRunLoop currentRunLoop] currentMode]);
}];
//timer 添加到 runloop 的 commonModes
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
滾動(dòng)頁(yè)面輸出:
runloopMode: kCFRunLoopDefaultMode
//主線程runloop modes
modeArray: (
UITrackingRunLoopMode,
GSEventReceiveRunLoopMode,
kCFRunLoopDefaultMode,
kCFRunLoopCommonModes
)
//頁(yè)面靜止
timerWithTimeInterval mode: kCFRunLoopDefaultMode
//頁(yè)面滾動(dòng)
scrollViewDidScroll: UITrackingRunLoopMode
scrollViewDidScroll: UITrackingRunLoopMode
timerWithTimeInterval mode: UITrackingRunLoopMode
scrollViewDidScroll: UITrackingRunLoopMode
scrollViewDidEndDecelerating: UITrackingRunLoopMode
//頁(yè)面靜止
timerWithTimeInterval mode: kCFRunLoopDefaultMode
頁(yè)面滾動(dòng)過(guò)程中處于UITrackingRunLoopMode
振惰,靜止?fàn)顟B(tài)處于kCFRunLoopDefaultMode
。
2.2 CFRunLoopTimerRef
@interface ViewController () {
CFRunLoopTimerRef timerRef;
}
@end
- (void)cfTimerTest {
/** CFRunLoopTimerContext timer 上下文
version: 版本
info: 傳遞參數(shù)
void *(*retain)(const void *info): retain 操作
void (*release)(const void *info): release 操作
(*copyDescription)(const void *info): copy 描述信息
*/
CFRunLoopTimerContext context = {
0,
((__bridge void *)self),
NULL,
NULL,
NULL
};
//獲取當(dāng)前 runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
/** CFRunLoopTimerCreate 創(chuàng)建 timer
allocator: 用于分配對(duì)象的內(nèi)存
fireDate:在什么是觸發(fā) (距離現(xiàn)在)
interval: 每隔多少時(shí)間觸發(fā)一次
flags: 未來(lái)參數(shù)
order: CFRunLoopObserver的優(yōu)先級(jí) 當(dāng)在Runloop同一運(yùn)行階段中有多個(gè)CFRunLoopObserver 正常情況下使用0
callout: 回調(diào),比如觸發(fā)事件垄懂。
context:上下文記錄信息
*/
//創(chuàng)建timer
timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 3, 0, 0, _runLoopTimerCallBack, &context);
CFRunLoopAddTimer(runloop, timerRef, kCFRunLoopCommonModes);
}
// CFRunLoopTimerCallBack typedef void (*CFRunLoopTimerCallBack)(CFRunLoopTimerRef timer, void *info);
void _runLoopTimerCallBack(CFRunLoopTimerRef timer, void *info) {// info 為 context 中的 info
NSLog(@"_runLoopTimerCallBack:%@,%@,%@",timer,info,[NSRunLoop currentRunLoop].currentMode);
}
- (void)dealloc {
[self invalidCFTimer];
}
// 移除 timer
- (void)invalidCFTimer {
CFRunLoopRef runloop = CFRunLoopGetCurrent();
if (timerRef && CFRunLoopContainsTimer(runloop, timerRef, kCFRunLoopCommonModes)) {
CFRunLoopRemoveTimer(runloop, timerRef, kCFRunLoopCommonModes);
}
}
-
CFRunLoopTimerContext
創(chuàng)建上下文用于傳遞參數(shù)骑晶,info
就是最終callback
回調(diào)中的info
信息痛垛。 -
CFRunLoopTimerCreate
主要是回調(diào)函數(shù)以及時(shí)間間隔的設(shè)置。 -
dealloc
中需要移除timer
透罢,不釋放有可能crash
榜晦,不會(huì)造成循環(huán)引用。需要判斷timer
是否存在以及是否在要移除的runloop
中羽圃。
2.3 CFRunLoopObserverRef
CFRunLoopObserverRef observerRef;
- (void)cfObserverTest {
//context 與 timer 的相同
CFRunLoopObserverContext context = {
0,
((__bridge void *)self),
NULL,
NULL,
NULL
};
CFRunLoopRef runloop = CFRunLoopGetCurrent();
/**
allocator: 用于分配對(duì)象的內(nèi)存
activities: 要關(guān)注的事件乾胶,與 runloop 循環(huán)中處理的狀態(tài)一致。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
repeats: CFRunLoopObserver 是否循環(huán)調(diào)用朽寞,不循環(huán)只會(huì)有一次回調(diào)识窿。NO的情況下 CFRunLoopObserverRef 會(huì)自動(dòng)移除。
order: CFRunLoopObserver 的優(yōu)先級(jí) 當(dāng)在Runloop同一運(yùn)行階段中有多個(gè) CFRunLoopObserver 正常情況下使用 0
callout: 回調(diào)
context: 上下文記錄信息
*/
observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, NO, 0, _runLoopObserverCallBack, &context);
CFRunLoopAddObserver(runloop, observerRef, kCFRunLoopDefaultMode);
}
//typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
void _runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
NSLog(@"observerCallBack: %@,%lu,%@",observer,activity,info);
}
- (void)removeRunloopObserver {
CFRunLoopRef runloop = CFRunLoopGetCurrent();
if (observerRef && CFRunLoopContainsObserver(runloop, observerRef, kCFRunLoopDefaultMode)) {
CFRunLoopRemoveObserver(runloop, observerRef, kCFRunLoopDefaultMode);
}
}
- (void)dealloc {
[self removeRunloopObserver];
}
-
CFRunLoopObserverContext
與CFRunLoopTimerContext
結(jié)構(gòu)相同脑融。 -
CFRunLoopObserverCreate
可以只監(jiān)聽(tīng)某些狀態(tài)喻频。-
repeats
控制是否循環(huán)調(diào)用,不循環(huán)的情況下回調(diào)只會(huì)調(diào)用一次(所有狀態(tài)假加起來(lái))肘迎,并且會(huì)自動(dòng)從runloop
中移除甥温。
-
可以通過(guò)
observer
進(jìn)行卡頓檢測(cè)相關(guān)邏輯處理。
2.4 CFRunLoopSourceRef
2.4.1 source0
- (void)source0Test {
/**
typedef struct {
CFIndex version; //版本
void * info; //回調(diào)info信息
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2); //判等
CFHashCode (*hash)(const void *info); //hash code
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode); //準(zhǔn)備代發(fā)回調(diào)
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode); //取消回調(diào)
void (*perform)(void *info); // 執(zhí)行回調(diào)
} CFRunLoopSourceContext;
*/
CFRunLoopSourceContext context = {
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
schedule,
cancel,
perform,
};
/**
allocator: 傳遞NULL或 kCFAllocatorDefault 以使用當(dāng)前默認(rèn)分配器妓布。
order: 優(yōu)先級(jí)索引姻蚓,指示處理運(yùn)行循環(huán)源的順序。這里傳0為了的就是自主回調(diào)
context: 為運(yùn)行循環(huán)源保存上下文信息的結(jié)構(gòu)
*/
//創(chuàng)建source
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//source 指定了 runloop 與 mode 就進(jìn)去就緒狀態(tài)了匣沼。schedule 調(diào)用
CFRunLoopAddSource(runloop, source0, kCFRunLoopDefaultMode);
//一個(gè)執(zhí)行信號(hào) perform
CFRunLoopSourceSignal(source0);
//這里加延遲為了讓 perform 有機(jī)會(huì)執(zhí)行狰挡。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//喚醒 runloop 防止沉睡狀態(tài)
CFRunLoopWakeUp(runloop);
//取消移除 source,執(zhí)行 cancel 回調(diào)
CFRunLoopRemoveSource(runloop, source0, kCFRunLoopDefaultMode);
});
}
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
NSLog(@"schedule 準(zhǔn)備代發(fā)");
}
void perform(void *info){
NSLog(@"source0 執(zhí)行");
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
NSLog(@"source0 cancel");
}
輸出:
schedule 準(zhǔn)備代發(fā)
source0 執(zhí)行
source0 cancel
-
CFRunLoopSourceContext
相比timer
和observer
多了判等以及哈希相關(guān)計(jì)算外释涛。更重要的是schedule 加叁、cancel 、perform 3
個(gè)回調(diào)唇撬。 -
CFRunLoopSourceCreate
相比就比較簡(jiǎn)單了它匕,重要參數(shù)是context
。
2.4.2 source1 線程間通信
@interface ViewController ()<NSPortDelegate>
@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;
@end
- (void)source1Test {
self.mainThreadPort = [NSPort port];
self.mainThreadPort.delegate = self;
// port - source1 -- runloop
[[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
[self task];
}
- (void)task {
__weak typeof(self) weakSelf = self;
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"initWithBlock:%@", [NSThread currentThread]); // 5
weakSelf.subThreadPort = [NSPort port];
weakSelf.subThreadPort.delegate = weakSelf;
[[NSRunLoop currentRunLoop] addPort:weakSelf.subThreadPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
}
#pragma mark --- NSPortDelegate ---
- (void)handlePortMessage:(id)message {//NSPortMessage 是macos中的類窖认。
NSLog(@"handlePortMessage: %@, thread:%@",message,[NSThread currentThread]);
NSArray *componentsArray = [message valueForKey:@"components"];
for (NSInteger i = 0; i < componentsArray.count; i++) {
NSData *data = componentsArray[i];
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"dataStr: %@",dataStr);
}
//runtime 獲取ivar超凳。
// unsigned int count = 0;
// Ivar *ivars = class_copyIvarList([message class], &count);
// for (int i = 0; i < count; i++) {
// NSString *name = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
// NSLog(@"data: %@",name);
// }
sleep(1);
if (![[NSThread currentThread] isMainThread]) {
NSMutableArray *components = [NSMutableArray array];
//必須轉(zhuǎn)成data,否則會(huì)被忽略耀态。
NSData *data = [@"hotpot" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
//從 subport 給 mainport 發(fā)送數(shù)據(jù)轮傍。
NSLog(@"mainport send data to subport");
[self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"cat" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
//從 mainport 給 subThreadPort 發(fā)送數(shù)據(jù)。
NSLog(@"subport send data to mainport");
[self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
輸出:
-
mainThreadPort
是在主線程創(chuàng)建添加進(jìn)runloop
的首装,subThreadPort
是在子線程創(chuàng)建添加進(jìn)runloop
的创夜。 -
handlePortMessage :
為NSPortDelegate
的代理方法。參數(shù)NSPortMessage
是定義在macOS
中的仙逻。iOS
沒(méi)有該類的定義驰吓。 - 通過(guò)
sendBeforeDate:components:from: reserved :
進(jìn)行線程間數(shù)據(jù)傳遞涧尿。從from port
給調(diào)用方port
線程發(fā)送數(shù)據(jù)。 - 數(shù)據(jù)需要包裝為
NSData
才有效檬贰。
NSPortMessage
定義在macOS
中:
image.png
三姑廉、runloop 結(jié)構(gòu)
既然runloop
是一個(gè)事件循環(huán),那么它與普通的循環(huán)有什么區(qū)別呢翁涤?
普通循環(huán):
runloop
循環(huán):
那么可以得到以下結(jié)論:
-
runloop
能保持程序的持續(xù)運(yùn)行桥言。 - 處理
APP
中的各種事件(觸摸、定時(shí)器葵礼、performSelector
)号阿。 - 節(jié)省
cpu
資源、提高程序的性能鸳粉。有休眠和喚醒狀態(tài)扔涧。
3.1 runloop 源碼定位
那么runloop
是怎么做到的呢?
通常我們會(huì)通過(guò)NSRunLoop
去獲取當(dāng)前的runloop
:
[NSRunLoop currentRunLoop];
定義如下:
@property (class, readonly, strong) NSRunLoop *currentRunLoop;
給currentRunLoop
下符號(hào)斷點(diǎn):
可以看到
NSRunLoop
是對(duì)CFRunLoop
的封裝届谈。
通過(guò)之前的分析已經(jīng)定位到了runloop
是在CoreFoundation
中的 CoreFoundation源碼枯夜。正好CoreFoundation
開(kāi)源了CFRunLoop
:
3.2 CFRunLoopRun
//CFRunLoopRun 是對(duì) do...while 的封裝
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
//run
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
//不是完成或者結(jié)束
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
-
runloop
底層是對(duì)do...while
的封裝。 - 當(dāng)狀態(tài)為完成或者結(jié)束后退出循環(huán)艰山。
那么核心邏輯就在CFRunLoopRunSpecific
中湖雹。還有一個(gè)疑問(wèn)是runloop
可以休眠,那么它是如何實(shí)現(xiàn)的呢程剥?
3.3 runloop 數(shù)據(jù)結(jié)構(gòu)
要了解runloop
的實(shí)現(xiàn)原理劝枣,首先要清楚它的數(shù)據(jù)結(jié)構(gòu)汤踏。
3.3.1 線程與runloop的關(guān)系
CFRunLoopRunSpecific
的第一個(gè)參數(shù)是CFRunLoopGetCurrent()
:
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
//通過(guò) key-value 形式獲取 CFRunLoopRef
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
//沒(méi)有緩存通過(guò)線程獲取
return _CFRunLoopGet0(pthread_self());
}
- 去緩存中獲取
CFRunLoopRef
织鲸。 - 緩存中不存在通過(guò)線程去獲取。
_CFRunLoopGet0
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//沒(méi)有傳 pthread_t溪胶,則默認(rèn)為主線程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
//創(chuàng)建可變字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//通過(guò) 主線程 創(chuàng)建 mainLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 進(jìn)行綁定搂擦,存儲(chǔ) thread(key) - runloop(value): dict[@"pthread_main_thread_np"] = mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//通過(guò) thread 獲取 runloop(非main runloop)
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {//runloop不存在
//創(chuàng)建runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//存儲(chǔ) runloop
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;
}
-
runloop
與線程是一一對(duì)應(yīng)的,每個(gè)runloop
對(duì)應(yīng)一個(gè)線程哗脖。線程并不一定有runloop
瀑踢,在有的情況下是一一對(duì)應(yīng)的。 -
runloop
底層是存儲(chǔ)在可變字典中的才避,key
為線程橱夭,value
為runloop
。 -
runloop
是CFRunLoopRef
類型桑逝,通過(guò)__CFRunLoopCreate
創(chuàng)建棘劣。
3.3.2 CFRunLoopRef
可以看到在創(chuàng)建
CFRunLoopRef
的時(shí)候有對(duì)應(yīng)的modes
、items
楞遏、_pthread
茬暇。并且創(chuàng)建完成后調(diào)用__CFRunLoopFindMode
去找mode
首昔。
CFRunLoopRef
的定義如下:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
實(shí)際上底層它是__CFRunLoop
類型:
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
pthread_t _pthread;//線程
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;//集合類型 Items
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;//集合類型 Modes
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
- 一個(gè)
runloop
對(duì)應(yīng)多個(gè)modes
以及items
。
對(duì)于timer
而言:
[[NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSRunLoopMode)#>];
顯然它是要依賴mode
的糙俗。
CFRunLoopMode
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;//set source0
CFMutableSetRef _sources1;//set source1
CFMutableArrayRef _observers;//array observe
CFMutableArrayRef _timers;//array times
CFMutableDictionaryRef _portToV1SourceMap;//dic port
__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 */
};
而一個(gè)mode
下又對(duì)應(yīng)多個(gè)items(source0勒奇、source1、timers巧骚、observers)
赊颠,所以就有如下關(guān)系:
1
個(gè)runloop
對(duì)應(yīng)1
個(gè)線程。1
個(gè)runloop
對(duì)應(yīng)多個(gè)mode
网缝。1
個(gè)mode
對(duì)應(yīng)多個(gè)source
巨税、timer
、observer
粉臊。
3.3.3 RunLoop Modes
既然有多種mode
草添,那么都有哪些呢?
源碼中有如下定義:
CF_EXPORT const CFRunLoopMode kCFRunLoopDefaultMode;
CF_EXPORT const CFRunLoopMode kCFRunLoopCommonModes;
它們對(duì)應(yīng)Foundation
中的:
FOUNDATION_EXPORT NSRunLoopMode const NSDefaultRunLoopMode;
FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes ;
我們都清楚在頁(yè)面滾動(dòng)的時(shí)候有一個(gè)UITrackingRunLoopMode
:
UIKIT_EXTERN NSRunLoopMode const UITrackingRunLoopMode;
除了以上3
種mode
還有兩個(gè)私有mode
:
UIInitializationRunLoopMode
GSEventReceiveRunLoopMode
當(dāng)RunLoop
運(yùn)行在Mode1
上時(shí)扼仲,是無(wú)法接受處理Mode2
或Mode3
上的Source远寸、Timer、Observer
事件的屠凶。
-
kCFRunLoopDefaultMode/NSDefaultRunLoopMode
:默認(rèn)模式驰后,主線程是在這個(gè)運(yùn)行模式下運(yùn)行。 -
kCFRunLoopCommonModes/NSRunLoopCommonModes
:偽模式矗愧,不是一種真正的運(yùn)行模式灶芝,是同步Source/Timer/Observer
到多個(gè)Mode
中。 -
UITrackingRunLoopMode
:跟蹤用戶交互事件(用于ScrollView
追蹤觸摸滑動(dòng)唉韭,保證界面滑動(dòng)時(shí)不受其他Mode
影響)夜涕。對(duì)于macOS
對(duì)應(yīng)NSEventTrackingRunLoopMode
-
UIInitializationRunLoopMode
:在剛啟動(dòng)App
時(shí)第進(jìn)入的第一個(gè)Mode
,啟動(dòng)完成后就不再使用属愤。 -
GSEventReceiveRunLoopMode
:接受系統(tǒng)內(nèi)部事件女器,通常用不到。
四住诸、runloop 事務(wù)處理
4.1 timer 事務(wù)處理
以timer
為例驾胆,將timer
加入到runloop
中:
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timerWithTimeInterval block -- %@",[[NSRunLoop currentRunLoop] currentMode]);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
底層調(diào)用了CFRunLoopAddTimer
:
4.1.1 CFRunLoopAddTimer
//runloop timer mode
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
__CFRunLoopLock(rl);
//kCFRunLoopCommonModes 集合mode
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//將 timer 加入 modeItems 中。
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
//加入所有的command modes 中贱呐。
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {//非common mode
//從 runloop 中找到對(duì)應(yīng)的 mode
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
if (NULL != rlm) {//mode存在
if (NULL == rlm->_timers) {//不存在timer
CFArrayCallBacks cb = kCFTypeArrayCallBacks;
cb.equal = NULL;
//創(chuàng)建timer
rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
}
}
if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
__CFRunLoopTimerLock(rlt);
//timer沒(méi)有runloop
if (NULL == rlt->_runLoop) {
//設(shè)置runloop
rlt->_runLoop = rl;
} else if (rl != rlt->_runLoop) {//timer所有的runloop與當(dāng)前runloop不同直接返回丧诺。
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
return;
}
//將timer 加入 mode中。
CFSetAddValue(rlt->_rlModes, rlm->_name);
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
__CFRepositionTimerInMode(rlm, rlt, false);
__CFRunLoopTimerFireTSRUnlock();
if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
// Normally we don't do this on behalf of clients, but for
// backwards compatibility due to the change in timer handling...
//喚醒runloop
if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
}
根據(jù)要加入的mode
區(qū)分是common mode
和非common mode
將timer
加入mode
中奄薇。這個(gè)時(shí)候只是將timer
加入了mode
中驳阎,要執(zhí)行肯定要調(diào)用CFRunLoopRun
,最終要調(diào)用CFRunLoopRunSpecific
。
4.1.2 CFRunLoopRunSpecific
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
//對(duì)應(yīng)聲明
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
-
CFRunLoopGetCurrent()
創(chuàng)建runloop
搞隐。 -
mode
默認(rèn)給的是kCFRunLoopDefaultMode
驹愚。 -
1.0e10
(1 * 1010)表示超時(shí)時(shí)間。 -
returnAfterSourceHandled
表示source
處理后是否返回劣纲。
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
//根據(jù) modeName 找到本次運(yùn)行的 mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//如果沒(méi)找到 || mode中沒(méi)有注冊(cè)任何事件逢捺,則就此停止,不進(jìn)入循環(huán)
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);
//取上一次運(yùn)行的 mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//如果本次mode和上次的mode一致
rl->_currentMode = currentMode;
//初始化一個(gè)result為kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry )
// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop癞季。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//run
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;
}
- 根據(jù)
modeName
找到本次運(yùn)行的mode
。 - 1.通知
Observers
RunLoop
即將進(jìn)入loop
绷柒。 - 調(diào)用
__CFRunLoopRun
運(yùn)行循環(huán)志于。 - 10.通知
Observers
RunLoop
即將退出。
4.1.3 __CFRunLoopRun
在__CFRunLoopRun
中調(diào)用了__CFRunLoopDoTimers
:
// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */
Boolean timerHandled = false;
CFMutableArrayRef timers = NULL;
//從mode中獲取timers進(jìn)行處理
for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
if (rlt->_fireTSR <= limitTSR) {
if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(timers, rlt);
}
}
}
for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
//執(zhí)行timer
Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
timerHandled = timerHandled || did;
}
if (timers) CFRelease(timers);
return timerHandled;
}
找到mode
中的所有timer
然后調(diào)用__CFRunLoopDoTimer
废睦。
4.1.4 __CFRunLoopDoTimer
在
__CFRunLoopDoTimer
中進(jìn)行了時(shí)間的判斷以及timer
回調(diào)的調(diào)用并且重新計(jì)算了_fireTSR
伺绽。這樣整個(gè)調(diào)用流程就與回調(diào)堆棧吻合了。
CFRunLoopAddTimer -> CFRunLoopRunSpecific -> __CFRunLoopRun -> __CFRunLoopDoTimers -> __CFRunLoopDoTimer -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
嗜湃。
4.2 source 事務(wù)處理
與timer
相同source
會(huì)調(diào)用CFRunLoopAddSource
:
將
source
加入mode
中奈应。同樣調(diào)用是在__CFRunLoopRun
中。
4.2.1 __CFRunLoopDoSources0
在
__CFRunLoopDoSources0
中調(diào)用了__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
购披。
4.2.2 __CFRunLoopDoSources1
__CFRunLoopDoSources1
最終調(diào)用了__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
CFRunLoopAddSource -> CFRunLoopRunSpecific -> __CFRunLoopRun -> __CFRunLoopDoSources0/__CFRunLoopDoSources1 -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ /__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
4.3 observer 事務(wù)處理
同理observer
會(huì)調(diào)用CFRunLoopAddObserver
杖挣。
4.3.1 CFRunLoopAddObserver
將
observer
加入_observers
或者_commonModeItems
中。同樣調(diào)用是在__CFRunLoopRun
中刚陡。
4.3.2 __CFRunLoopDoObservers
__CFRunLoopDoObservers
最終調(diào)用__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
傳遞了狀態(tài)參數(shù)惩妇。
CFRunLoopAddObserver -> CFRunLoopRunSpecific -> __CFRunLoopRun -> __CFRunLoopDoObservers(狀態(tài)參數(shù)) -> __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(狀態(tài)參數(shù))
4.4 回調(diào)函數(shù)
4.4.1 CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
if (func) {
func(timer, info);
}
asm __volatile__(""); // thwart tail-call optimization
}
timer
直接調(diào)用回調(diào)函數(shù),傳遞timer
參數(shù)筐乳。
4.4.2 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) {
if (perform) {
perform(info);
}
asm __volatile__(""); // thwart tail-call optimization
}
source0
直接調(diào)用perform
歌殃。
4.4.3 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply,
#else
void (*perform)(void *),
#endif
void *info) {
if (perform) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
*reply = perform(msg, size, kCFAllocatorSystemDefault, info);
#else
perform(info);
#endif
}
asm __volatile__(""); // thwart tail-call optimization
}
source1
不同架構(gòu)處理不同,iOS
上與source0
處理相同哥童。
4.4.4 CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (func) {
func(observer, activity, info);
}
asm __volatile__(""); // thwart tail-call optimization
}
observer
調(diào)用回調(diào)函數(shù)其中傳遞了activity
狀態(tài)參數(shù)挺份。
4.4.5 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) {
_dispatch_main_queue_callback_4CF(msg);
asm __volatile__(""); // thwart tail-call optimization
}
直接調(diào)用_dispatch_main_queue_callback_4CF
褒翰。
4.5.6 source1 與 source0
點(diǎn)擊觸摸事件會(huì)先調(diào)用source1
:
然后交給source0
處理:
之后才會(huì)進(jìn)入觸摸回調(diào)贮懈。
系統(tǒng)先通過(guò)source1
喚醒runloop
然后交給source0
處理事件。
五优训、runloop 原理
在CFRunLoopRun
的過(guò)程中do...while
的條件是根據(jù)返回的狀態(tài)判斷的:
enum {
kCFRunLoopRunFinished = 1,//完成
kCFRunLoopRunStopped = 2,//結(jié)束
kCFRunLoopRunTimedOut = 3,//超時(shí)
kCFRunLoopRunHandledSource = 4//處理完source
};
在CFRunLoopRunSpecific
的過(guò)程中也有狀態(tài)的切換:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//啟動(dòng)
kCFRunLoopBeforeTimers = (1UL << 1),//將要處理 timer 事件
kCFRunLoopBeforeSources = (1UL << 2),//將要處理 Source 事件
kCFRunLoopBeforeWaiting = (1UL << 5),//將要進(jìn)入休眠狀態(tài),即將由用戶態(tài)切換到內(nèi)核態(tài)
kCFRunLoopAfterWaiting = (1UL << 6),//被喚醒朵你,即從內(nèi)核態(tài)切換到用戶態(tài)后
kCFRunLoopExit = (1UL << 7),//退出
kCFRunLoopAllActivities = 0x0FFFFFFFU //監(jiān)聽(tīng)所有狀態(tài)
};
可以通過(guò)CFRunLoopActivity
監(jiān)聽(tīng)整個(gè)runloop
的生命周期。
runloop
的整個(gè)核心邏輯就在__CFRunLoopRun
中:
整個(gè)流程偽代碼如下:
//獲取 mode 處理
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
/// 首先根據(jù)modeName找到對(duì)應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 1.通知 Observers: RunLoop 即將進(jìn)入 loop揣非。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù)抡医,進(jìn)入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
/// 10.通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
//核心函數(shù)
/**
* 運(yùn)行run loop
*
* @param rl 運(yùn)行的RunLoop對(duì)象
* @param rlm 運(yùn)行的mode
* @param seconds run loop超時(shí)時(shí)間
* @param stopAfterHandle true:run loop處理完事件就退出 false:一直運(yùn)行直到超時(shí)或者被手動(dòng)終止
* @param previousMode 上一次運(yùn)行的mode
*
* @return 返回4種狀態(tài)
*/
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//一些超時(shí)邏輯相關(guān)的處理
int32_t retVal = 0;
do { //itmes do
/// 2. 通知 Observers: 即將處理timer事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
/// 3.通知 Observers: 即將處理Source0(非port)事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
/// 執(zhí)行被加入的 block
__CFRunLoopDoBlocks(rl, rlm);
/// 4.處理sources0 (非port)事件
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
/// 處理sources0返回為YES
if (sourceHandledThisLoop) {
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
/// 5.如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息(9)忌傻。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
/// 處理消息
goto handle_msg;
}
/// 6.通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)大脉。
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 設(shè)置RunLoop為休眠狀態(tài)。
__CFRunLoopSetSleeping(rl);
// 內(nèi)循環(huán)水孩,用于接收等待端口的消息
// 進(jìn)入此循環(huán)后镰矿,線程進(jìn)入休眠,直到收到新消息才跳出該循環(huán)俘种,繼續(xù)執(zhí)行run loop
do {
/// 7.等待被喚醒, 調(diào)用 mach_msg 等待接受 mach_port 的消息秤标。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
/// 7.1 一個(gè)基于 port 的 Source1 的事件宙刘。
/// 7.2 一個(gè) Timer 到時(shí)間了
/// 7.3 RunLoop 自身的超時(shí)時(shí)間到了
/// 7.4 被其他什么調(diào)用者手動(dòng)喚醒
// mach 事務(wù) - 指令
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while(1)
// 取消runloop的休眠狀態(tài)苍姜,也就是喚醒。
__CFRunLoopUnsetSleeping(rl);
/// 8.通知 Observers: RunLoop 的線程剛剛被喚醒了悬包。
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
/// 9.處理喚醒時(shí)收到的消息衙猪,之后跳轉(zhuǎn) 步驟2
handle_msg:
//9.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)布近。
if (被Timer喚醒) {
//處理Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())屈嗤;
} else if (被GCD喚醒) {
//9.2 如果有dispatch到main_queue的block,執(zhí)行block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else if (被Source1喚醒) {
//9.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了吊输,處理這個(gè)事件饶号。(被source1喚醒)
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
//進(jìn)入loop時(shí)參數(shù)說(shuō)處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
//超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
//被外部調(diào)用者強(qiáng)制停止了
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
//自動(dòng)停止了
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//source/timer/observer一個(gè)都沒(méi)有了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
根據(jù)源碼有以下流程圖:
同時(shí)官方文檔中也給出了流程總結(jié):
5.1 runloop 休眠與喚醒
在第7
步前后分別調(diào)用了__CFRunLoopSetSleeping
與__CFRunLoopUnsetSleeping
進(jìn)行休眠與喚醒操作:
這里是通過(guò)Darwin
中的Mach
來(lái)進(jìn)行內(nèi)核態(tài)和用戶態(tài)的切換:
通過(guò)mach_msg()
函數(shù)接收季蚂、發(fā)送消息茫船,本質(zhì)上是調(diào)用mach_msg_trap()
。在用戶態(tài)調(diào)用 mach_msg_trap()
時(shí)會(huì)切換到內(nèi)核態(tài)扭屁,內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的mach_msg()
函數(shù)會(huì)完成實(shí)際的工作算谈。
比如觸摸屏幕摸到硬件(屏幕)將事件先包裝成Event
告訴source1(port)
,source1
喚醒RunLoop
然后將事件Event
分發(fā)給source0
由source0
來(lái)處理料滥。
5.2 mode 切換
默認(rèn)情況下runloop
運(yùn)行后是在kCFRunLoopDefaultMode
模式下的然眼,那么runloop
是如何切換mode
的呢?
既然要切換mode
那么肯定要改變CFRunLoopRunSpecific
的參數(shù)葵腹,搜索CFRunLoopRunSpecific
后有在CFRunLoopRunInMode
中有調(diào)用:
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
而CFRunLoopRunInMode
并沒(méi)有找到調(diào)用的時(shí)機(jī)高每,給CFRunLoopRunInMode
下符號(hào)斷點(diǎn):
可以看到是在-[NSRunLoop(NSRunLoop) runMode:beforeDate:]
中以及GSEventRunModal
中進(jìn)行切換的。
六践宴、runloop 應(yīng)用場(chǎng)景
6.1 runloop 與 mode
6.1.1 mode 與 timer
常用的一個(gè)場(chǎng)景是在TableView
中的定時(shí)器在滾動(dòng)的時(shí)候回調(diào)是不執(zhí)行的鲸匿。因?yàn)檫@個(gè)時(shí)候runloop
的mode
從NSDefaultRunLoopMode
切換到了UITrackingRunLoopMode
。而timer
默認(rèn)情況下是加在NSDefaultRunLoopMode
下的阻肩。
這個(gè)時(shí)候就需要將timer
同時(shí)加到NSDefaultRunLoopMode
與UITrackingRunLoopMode
下:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
加入NSRunLoopCommonModes
下就可以了带欢。當(dāng)然也可以使用GCD
的timer
實(shí)現(xiàn)計(jì)時(shí)器。
6.1.2 mode 與 頁(yè)面刷新
怎樣保證子線程數(shù)據(jù)回來(lái)更新UI
的時(shí)候不打斷用戶的滑動(dòng)操作?
不打斷用戶操作那么當(dāng)runloop
在NSDefaultRunLoopMode
模式的時(shí)候頁(yè)面就不在滑動(dòng)狀態(tài)乔煞。那么就當(dāng)主線程RunLoop
由UITrackingRunLoopMode
切換到NSDefaultRunLoopMode
時(shí)再去更新UI
:
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
6.2 runloop 與 線程
RunLoop
與線程時(shí)是一一對(duì)應(yīng)的吁朦,數(shù)據(jù)以key(線程)-value(runloop)
存儲(chǔ)在全局的字典中。默認(rèn)情況下線程時(shí)不開(kāi)啟runloop
的(主線程除外)渡贾。
6.2.1 線程 與 timer
有如下案例(在主線程調(diào)用):
- (void)testRunloop {
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(performSelectorAction) withObject:nil afterDelay:10];
NSLog(@"3");
});
NSLog(@"4");
}
- (void)performSelectorAction {
NSLog(@"5");
}
輸出:
1
4
2
3
由于performSelector
帶了延遲函數(shù)(即使延遲時(shí)間為0
)霸褒,內(nèi)部創(chuàng)建了timer
香伴,而子線程沒(méi)有開(kāi)啟runloop
添加timer
會(huì)失敗,該方法也就失效了。
當(dāng)然如果是主隊(duì)列則沒(méi)有問(wèn)題颤芬,同步函數(shù)則結(jié)果依賴調(diào)用同步函數(shù)的線程渣刷。
修改代碼如下:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[[NSRunLoop currentRunLoop] run];
[self performSelector:@selector(performSelectorAction) withObject:nil afterDelay:10];
NSLog(@"3");
});
這個(gè)時(shí)候performSelectorAction
仍然不執(zhí)行煎饼,run
后runloop
中沒(méi)有事務(wù)導(dǎo)致runloop
退出了位岔。退出后再添加timer
顯然不會(huì)執(zhí)行。
那么將run
的邏輯在添加timer
后就可以了:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(performSelectorAction) withObject:nil afterDelay:10];
[[NSRunLoop currentRunLoop] run];
NSLog(@"3");
});
這個(gè)時(shí)候輸出:
1
4
2
5
3
這個(gè)時(shí)候即使delay
傳0
也沒(méi)問(wèn)題牺丙,進(jìn)一步說(shuō)明了NStimer
是不準(zhǔn)的则涯。
那么有個(gè)疑問(wèn),既然performSelector: withObject : afterDelay:
底層是對(duì)timer
的封裝冲簿,那么肯定會(huì)調(diào)用CFRunLoopAddTimer
粟判。而runloop
不存在的情況下這個(gè)函數(shù)會(huì)直接返回:
按照理解應(yīng)該先創(chuàng)建
runloop
然后添加timer
再啟動(dòng):
[NSRunLoop currentRunLoop];
[self performSelector:@selector(performSelectorAction) withObject:nil afterDelay:0];
[[NSRunLoop currentRunLoop] run];
為什么先添加timer
也沒(méi)問(wèn)題呢?
performSelector: withObject : afterDelay:
內(nèi)部會(huì)先創(chuàng)建runloop
然后添加timer
峦剔,我們只需要在添加timer
后啟動(dòng)runloop
档礁。- 自己?jiǎn)?dòng)
RunLoop
,一定要在添加item
后吝沫。
6.2.2 線程常駐
線程鄙肜剑活在實(shí)際開(kāi)發(fā)中經(jīng)常會(huì)遇到一些耗時(shí)且需要頻繁處理的工作,這些工作和UI
無(wú)關(guān)惨险,比如大文件的下載羹幸、后臺(tái)進(jìn)行數(shù)據(jù)的上報(bào)等。線程常駐的好處是不用頻繁的開(kāi)辟銷毀線程節(jié)省資源辫愉。
6.2.2.1 線程釋放驗(yàn)證
創(chuàng)建一個(gè)HPThread
繼承自NSThread
栅受,只重寫dealloc
方便驗(yàn)證線程是否銷毀:
- (void)dealloc {
NSLog(@"%s",__func__);
}
調(diào)用如下:
HPThread *thread = [[HPThread alloc] initWithTarget:self selector:@selector(threadAction) object:nil];
[thread start];
- (void)threadAction {
@autoreleasepool {
for (int i = 0; i < 100; i++) {
NSLog(@"子線程任務(wù) ??????????????????????????????%d - %@",i,[NSThread currentThread]);
}
NSLog(@"??????????????子線程任務(wù)結(jié)束- %@",[NSThread currentThread]);
}
}
這個(gè)時(shí)候在threadAction
中任務(wù)執(zhí)行完畢后HPThread
就釋放了:
這個(gè)時(shí)候如果HPThread
改為屬性被持有:
@property (nonatomic, strong) HPThread *thread;
HPThread
的dealloc
就不會(huì)執(zhí)行了,那么這個(gè)時(shí)候線程釋放了么恭朗?
創(chuàng)建一個(gè)新任務(wù)在self. thread
中執(zhí)行:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
[self.view addGestureRecognizer:tap];
- (void)tapAction:(UITapGestureRecognizer *)tap {
//waitUntilDone:YES ??????????????????????????????執(zhí)行完 otherChildThreadAction 才執(zhí)行后續(xù)的邏輯屏镊,為 NO 就一起直接執(zhí)行了。
[self performSelector:@selector(otherChildThreadAction) onThread:self.thread withObject:nil waitUntilDone:NO];
NSLog(@"單點(diǎn)事件執(zhí)行完畢");
}
- (void)otherChildThreadAction {
@autoreleasepool {
for (int i = 0; i < 10; i++) {
NSLog(@"otherChildThreadAction 子線程任務(wù) ??????????????????????????????%d - %@",i,[NSThread currentThread]);
}
NSLog(@"otherChildThreadAction ??????????????子線程任務(wù)結(jié)束- %@",[NSThread currentThread]);
}
}
這個(gè)時(shí)候otherChildThreadAction
并沒(méi)有執(zhí)行:
說(shuō)明
HPThread
創(chuàng)建的線程已經(jīng)釋放了冀墨。那么說(shuō)明僅僅持有HPThread
并不能保證線程存活闸衫。
6.2.2.2 線程碧喂幔活
顯然pthread_create
創(chuàng)建的線程在任務(wù)執(zhí)行完畢后就被釋放了诽嘉,要確保線程執(zhí)行完后不被釋放,那么就要持有它。那么就有兩種方式:
- 1.使用
pthread
代替HPThread
實(shí)現(xiàn)線程創(chuàng)建邏輯虫腋。 - 2.使用
runloop
持有thread
骄酗。為了讓thread
不釋放,runloop
要一直有事務(wù)悦冀。
使用 pthread
比較麻煩更好的方案是使用runloop
的方案趋翻。
修改threadAction
如下:
- (void)threadAction {
@autoreleasepool {
for (int i = 0; i < 100; i++) {
NSLog(@"子線程任務(wù) ??????????????????????????????%d - %@",i,[NSThread currentThread]);
}
// ??????????????????????????????????????????????????????????????????????????????????????????????????????RunLoop 任務(wù)執(zhí)行完畢后線程就銷毀了,線程焙畜。活需要加入 runloop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//為了 runloop 不退出
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
//end 不會(huì)執(zhí)行踏烙,因?yàn)榕躣o...while 循環(huán)了。
NSLog(@"??????????????子線程任務(wù)結(jié)束- %@",[NSThread currentThread]);
}
}
這樣就保證了線程不被釋放:
但是這個(gè)時(shí)候又存在一個(gè)問(wèn)題了历等。self -> thread -> self
造成了循環(huán)引用讨惩。需要在結(jié)束任務(wù)的時(shí)候退出線程打破循環(huán)引用:
@property (nonatomic, assign) BOOL stopped;
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapAction:)];
doubleTap.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTap];
- (void)doubleTapAction:(UITapGestureRecognizer *)tap {
[self performSelector:@selector(exitThread) onThread:self.thread withObject:nil waitUntilDone:NO];
NSLog(@"雙擊事件執(zhí)行完畢");
}
- (void)exitThread {
self.stopped = YES;
//????停止RunLoop,這樣只會(huì)停止當(dāng)次的寒屯。
CFRunLoopStop(CFRunLoopGetCurrent());
[self.thread cancel];
//????????????????打破循環(huán)引用
self.thread = nil;
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
thread
也可以使用- (instancetype)initWithBlock:(void (^)(void))block
來(lái)創(chuàng)建荐捻。雖然沒(méi)有循環(huán)引用了,vc
也能釋放寡夹,但是runloop
持有了thread
導(dǎo)致線程和runloop
不能釋放处面。
這個(gè)時(shí)候雙擊然后返回頁(yè)面仍然不能釋放VC
,由于runloop
是通過(guò)run
開(kāi)啟的(runUntilDate
)也一樣菩掏,run
一旦成功會(huì)不停的調(diào)用runMode:beforeDate:
來(lái)運(yùn)行runloop
魂角,而于CFRunLoopStop
只停止了一次runloop
(runloop
仍然持有了線程)。修改threadAction
如下:
- (void)threadAction {
@autoreleasepool {
for (int i = 0; i < 100; i++) {
NSLog(@"子線程任務(wù) ??????????????????????????????%d - %@",i,[NSThread currentThread]);
}
// ??????????????????????????????????????????????????????????????????????????????????????????????????????RunLoop 任務(wù)執(zhí)行完畢后線程就銷毀了智绸,線程被蚣眨活需要加入 runloop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//為了 runloop 不退出
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.stopped) {
// 這個(gè)方法在沒(méi)有任務(wù)時(shí)就睡眠 任務(wù)完成了就會(huì)退出loop
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
//不會(huì)執(zhí)行,因?yàn)榕躣o...while 循環(huán)了传于。 self.stopped
NSLog(@"??????????????子線程任務(wù)結(jié)束- %@",[NSThread currentThread]);
}
}
這個(gè)時(shí)候通過(guò)self.stopped
變量控制是否繼續(xù)run
就解決問(wèn)題了囱挑。
runMode: beforeDate:
只控制執(zhí)行一次:
image.png
但是當(dāng)我們將runMode: beforeDate:
的mode
修改為NSRunLoopCommonModes
后:
給
thread
添加任務(wù)也不執(zhí)行,并且cup
占滿沼溜。
等價(jià)于UITrackingRunLoopMode
:
所以在while
循環(huán)中不能一直run
在UITrackingRunLoopMode
模式平挑。
當(dāng)然也可以使用CFRunLoop
相關(guān)函數(shù)實(shí)現(xiàn):
// 創(chuàng)建上下文
CFRunLoopSourceContext context = {0};
// 創(chuàng)建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 銷毀source
CFRelease(source);
// 啟動(dòng)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
那么CFRunLoop
嘗試使用common modes
:
直接報(bào)錯(cuò)
CFRunLoopRunSpecific
模式只能傳遞特定模式。這也就是runMode: beforeDate:
啟動(dòng)后給thread
添加任務(wù)也不執(zhí)行的原因系草。
CFRunLoopRunInMode
與runMode: beforeDate:
只能運(yùn)行在特定的模式下通熄。CFRunLoopStop
只能退出單次的runloop
。
總結(jié):
-
runloop
是一個(gè)事件循環(huán)找都,分為內(nèi)核態(tài)和用戶態(tài)唇辨。底層是對(duì)do...while
的封裝。與do...while
的區(qū)別是它有休眠和喚醒邏輯能耻,從而節(jié)省cpu
資源赏枚、提高程序的性能亡驰。 -
NSRunloop
是對(duì)CFRunloop
的封裝。 -
runloop
與線程一一對(duì)應(yīng)饿幅,開(kāi)啟runloop
依賴于線程凡辱,線程不一定開(kāi)啟runloop
(主線程默認(rèn)開(kāi)啟,子線程需要手動(dòng)開(kāi)啟)栗恩。底層是存儲(chǔ)在可變字典中key
為線程透乾,value
為runloop
。 -
runloop
與mode
是一對(duì)多的關(guān)系磕秤。-
kCFRunLoopDefaultMode/NSDefaultRunLoopMode
:默認(rèn)模式乳乌。 -
UITrackingRunLoopMode
:跟蹤用戶交互事件。 -
kCFRunLoopCommonModes/NSRunLoopCommonModes
:偽模式市咆,不是一種真正的運(yùn)行模式钦扭,本質(zhì)上是同步Source/Timer/Observer
到多個(gè)Mode
中。
-
-
mode
與source
床绪、timer
客情、observer
也是一對(duì)多的關(guān)系。-
CFRunLoopSource
分為source0
與source1
癞己。-
source0
:基于非port
也就是用戶觸發(fā)的事件膀斋,需要手動(dòng)喚醒RunLoop
,將當(dāng)前線程從內(nèi)核態(tài)切換到用戶態(tài)痹雅。 -
source1
:基于port
仰担,包含一個(gè)mach_port
和一個(gè)回調(diào)〖ㄉ纾可監(jiān)聽(tīng)系統(tǒng)端口和通過(guò)內(nèi)核和其他線程發(fā)送的消息摔蓝,能主動(dòng)喚醒RunLoop
,接收分發(fā)系統(tǒng)事件愉耙。具備喚醒線程的能力贮尉。
-
-
CFRunLoopTimer
基于時(shí)間的觸發(fā)器,在預(yù)設(shè)的時(shí)間點(diǎn)喚醒RunLoop
執(zhí)行回調(diào)朴沿〔卵瑁基于runloop
所以不是實(shí)時(shí)的(RunLoop
只負(fù)責(zé)分發(fā)消息,如果線程當(dāng)前正在處理繁重的任務(wù)赌渣,有可能導(dǎo)致Timer
本次延時(shí)或者少執(zhí)行一次)魏铅。 -
CFRunLoopObserver
可以監(jiān)聽(tīng)runloop
的狀態(tài)。
-