OC內(nèi)存管理-runloop

一鸣剪、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)厌均。
image.png

runloop的官方文檔在thread篇章Run Loops,也就從側(cè)面說(shuō)明了runloop是與線程息息相關(guān)的饺鹃。

1.1 runloop 輸入源與處理機(jī)制

官方有如下一張圖:


`runloop`與`source`結(jié)構(gòu)

線程的輸入源:

  • Port Source:基于端口的輸入源。
  • Custom Source:自定義輸入源
  • performSelectorCocoa執(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");
}

timerperformSelector對(duì)應(yīng)的回調(diào)都是__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

image.png

后續(xù)的調(diào)用函數(shù)不同连锯。

block對(duì)應(yīng)__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

image.png

不過(guò)后續(xù)調(diào)用到了__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__归苍。

主線程對(duì)應(yīng)__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__:

image.png

系統(tǒng)觸摸事件對(duì)應(yīng)__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

image.png

通知事件對(duì)應(yīng)__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__

image.png

小結(jié):

  • 調(diào)用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__NSTimerperformSelector都屬于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];
}
  • CFRunLoopObserverContextCFRunLoopTimerContext結(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相比timerobserver多了判等以及哈希相關(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];
}

輸出:


image.png
  • 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):

image.png

runloop循環(huán):

image.png

那么可以得到以下結(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):

image.png

可以看到NSRunLoop是對(duì)CFRunLoop的封裝届谈。

image.png

通過(guò)之前的分析已經(jīng)定位到了runloop是在CoreFoundation中的 CoreFoundation源碼枯夜。正好CoreFoundation開(kāi)源了CFRunLoop

image.png

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為線程橱夭,valuerunloop
  • runloopCFRunLoopRef類型桑逝,通過(guò)__CFRunLoopCreate創(chuàng)建棘劣。
image.png

3.3.2 CFRunLoopRef

image.png

可以看到在創(chuàng)建CFRunLoopRef的時(shí)候有對(duì)應(yīng)的modesitems楞遏、_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)系:

image.png

  • 1個(gè)runloop對(duì)應(yīng)1個(gè)線程。
  • 1個(gè)runloop對(duì)應(yīng)多個(gè)mode网缝。
  • 1個(gè)mode對(duì)應(yīng)多個(gè)source巨税、timerobserver粉臊。

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;

除了以上3mode還有兩個(gè)私有mode

UIInitializationRunLoopMode
GSEventReceiveRunLoopMode

當(dāng)RunLoop運(yùn)行在Mode1上時(shí)扼仲,是無(wú)法接受處理Mode2Mode3上的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

image.png

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 modetimer加入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

image.png

__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

image.png

source加入mode中奈应。同樣調(diào)用是在__CFRunLoopRun中。

4.2.1 __CFRunLoopDoSources0

image.png

__CFRunLoopDoSources0中調(diào)用了__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__购披。

4.2.2 __CFRunLoopDoSources1

image.png

__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

image.png

observer加入_observers或者_commonModeItems中。同樣調(diào)用是在__CFRunLoopRun中刚陡。

4.3.2 __CFRunLoopDoObservers

image.png

__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

image.png

然后交給source0處理:

image.png

之后才會(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中:

image.png

整個(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ù)源碼有以下流程圖:


runloop循環(huán)流程

同時(shí)官方文檔中也給出了流程總結(jié):


image.png

5.1 runloop 休眠與喚醒

在第7步前后分別調(diào)用了__CFRunLoopSetSleeping__CFRunLoopUnsetSleeping進(jìn)行休眠與喚醒操作:

image.png

這里是通過(guò)Darwin中的Mach來(lái)進(jìn)行內(nèi)核態(tài)和用戶態(tài)的切換:

image.png

通過(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ā)給source0source0來(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):

image.png

image.png

可以看到是在-[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í)候runloopmodeNSDefaultRunLoopMode切換到了UITrackingRunLoopMode。而timer默認(rèn)情況下是加在NSDefaultRunLoopMode下的阻肩。
這個(gè)時(shí)候就需要將timer同時(shí)加到NSDefaultRunLoopModeUITrackingRunLoopMode下:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

加入NSRunLoopCommonModes下就可以了带欢。當(dāng)然也可以使用GCDtimer實(shí)現(xiàn)計(jì)時(shí)器。

6.1.2 mode 與 頁(yè)面刷新

怎樣保證子線程數(shù)據(jù)回來(lái)更新UI的時(shí)候不打斷用戶的滑動(dòng)操作?
不打斷用戶操作那么當(dāng)runloopNSDefaultRunLoopMode模式的時(shí)候頁(yè)面就不在滑動(dòng)狀態(tài)乔煞。那么就當(dāng)主線程RunLoopUITrackingRunLoopMode切換到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í)行煎饼,runrunloop中沒(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í)候即使delay0也沒(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ì)直接返回:

image.png

按照理解應(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)題呢?

image.png

  • 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就釋放了:

image.png

這個(gè)時(shí)候如果HPThread改為屬性被持有:

@property (nonatomic, strong) HPThread *thread;

HPThreaddealloc就不會(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í)行:

image.png

說(shuō)明HPThread創(chuàng)建的線程已經(jīng)釋放了冀墨。那么說(shuō)明僅僅持有HPThread并不能保證線程存活闸衫。

6.2.2.2 線程碧喂幔活

image.png

顯然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]);
    }
}

這樣就保證了線程不被釋放:


image.png

但是這個(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只停止了一次runlooprunloop仍然持有了線程)。修改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后:

image.png

thread添加任務(wù)也不執(zhí)行,并且cup占滿沼溜。

等價(jià)于UITrackingRunLoopMode

image.png

所以在while循環(huán)中不能一直runUITrackingRunLoopMode模式平挑。

當(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

image.png

直接報(bào)錯(cuò)CFRunLoopRunSpecific模式只能傳遞特定模式。這也就是runMode: beforeDate:啟動(dòng)后給thread添加任務(wù)也不執(zhí)行的原因系草。

  • CFRunLoopRunInModerunMode: 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為線程透乾,valuerunloop
  • runloopmode是一對(duì)多的關(guān)系磕秤。
    • kCFRunLoopDefaultMode/NSDefaultRunLoopMode:默認(rèn)模式乳乌。
    • UITrackingRunLoopMode:跟蹤用戶交互事件。
    • kCFRunLoopCommonModes/NSRunLoopCommonModes:偽模式市咆,不是一種真正的運(yùn)行模式钦扭,本質(zhì)上是同步Source/Timer/Observer到多個(gè)Mode中。
  • modesource床绪、timer客情、observer也是一對(duì)多的關(guān)系。
    • CFRunLoopSource分為source0source1癞己。
      • 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)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坚芜,一起剝皮案震驚了整個(gè)濱河市览芳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸿竖,老刑警劉巖沧竟,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铸敏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡屯仗,警方通過(guò)查閱死者的電腦和手機(jī)搞坝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門搔谴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)魁袜,“玉大人,你說(shuō)我怎么就攤上這事敦第》宓” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵芜果,是天一觀的道長(zhǎng)鞠呈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)右钾,這世上最難降的妖魔是什么蚁吝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮舀射,結(jié)果婚禮上窘茁,老公的妹妹穿的比我還像新娘。我一直安慰自己脆烟,他們只是感情好山林,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著邢羔,像睡著了一般驼抹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拜鹤,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天框冀,我揣著相機(jī)與錄音,去河邊找鬼敏簿。 笑死左驾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的极谊。 我是一名探鬼主播诡右,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼轻猖!你這毒婦竟也來(lái)了帆吻?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咙边,失蹤者是張志新(化名)和其女友劉穎猜煮,沒(méi)想到半個(gè)月后次员,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡王带,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年淑蔚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愕撰。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刹衫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搞挣,到底是詐尸還是另有隱情带迟,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布囱桨,位于F島的核電站仓犬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏舍肠。R本人自食惡果不足惜搀继,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翠语。 院中可真熱鬧叽躯,春花似錦、人聲如沸啡专。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)们童。三九已至畔况,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慧库,已是汗流浹背跷跪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留齐板,地道東北人吵瞻。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像甘磨,于是被迫代替她去往敵國(guó)和親橡羞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容