OC--RunLoop應(yīng)用例子

知識(shí)點(diǎn):
1、RunLoop的基礎(chǔ)知識(shí)
2、RunLoop 與 NSTimer
3、RunLoop 與 Perform Selector
4、RunLoop、線程、AutoreleasePool三者聯(lián)系
5、RunLoop 與 線程通信
6、RunLoop 的各種狀態(tài)監(jiān)聽
7、RunLoop 與 NSNotificationQueue

什么是RunLoop?

NSRunLoop蘋果官方文檔
CoreFoundation源碼

RunLoop入門 看我就夠了 - 簡(jiǎn)書
RunLoop已入門?不來(lái)應(yīng)用一下? - 簡(jiǎn)書

深入理解RunLoop | Garan no dou

RunLoop結(jié)構(gòu)圖
RunLoop跑圈圖
/// RunLoop的實(shí)現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
     
    /// 首先根據(jù)modeName找到對(duì)應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    /// 如果mode里沒(méi)有source/timer/observer, 直接返回。
    if (__CFRunLoopModeIsEmpty(currentMode)) return;
     
    /// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
     
    /// 內(nèi)部函數(shù),進(jìn)入loop
    __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
         
        Boolean sourceHandledThisLoop = NO;
        int retVal = 0;
        do {
  
            /// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
            /// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
            /// 執(zhí)行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);
             
            /// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
            sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
            /// 執(zhí)行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);
  
            /// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。
            if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) goto handle_msg;
            }
             
            /// 6.通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
            if (!sourceHandledThisLoop) {
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
            }
             
            /// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
            /// ? 一個(gè)基于 port 的Source 的事件。
            /// ? 一個(gè) Timer 到時(shí)間了
            /// ? RunLoop 自身的超時(shí)時(shí)間到了
            /// ? 被其他什么調(diào)用者手動(dòng)喚醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
            }
  
            /// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
             
            /// 9.收到消息,處理消息。
            handle_msg:
  
            /// 10.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)。
            if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            } 
  
            /// 10.2 如果有dispatch到main_queue的block,執(zhí)行block。
            else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 
  
            /// 10.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件
            else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                if (sourceHandledThisLoop) {
                    mach_msg(reply, MACH_SEND_MSG, reply);
                }
            }
             
            /// 執(zhí)行加入到Loop的block
            __CFRunLoopDoBlocks(runloop, currentMode);
             
  
            if (sourceHandledThisLoop && stopAfterHandle) {
                /// 進(jìn)入loop時(shí)參數(shù)說(shuō)處理完事件就返回。
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                /// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) {
                /// 被外部調(diào)用者強(qiáng)制停止了
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                /// source/timer/observer一個(gè)都沒(méi)有了
                retVal = kCFRunLoopRunFinished;
            }
             
            /// 如果沒(méi)超時(shí),mode里沒(méi)空,loop也沒(méi)被停止,那繼續(xù)loop。
        } while (retVal == 0);
    }
     
    /// 11. 通知 Observers: RunLoop 即將退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

RunLoop 有五種運(yùn)行模式,其中常見的有1.2兩種

  1. kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
  2. UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
  3. UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
  4. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
  5. kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode集合,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode

RunLoop應(yīng)用

1、NSTimer
init或者timerWithTimeInterval:方法

(1)需要手動(dòng)加入RunLoop

- (void)timer
{
   // 當(dāng)repeats = NO,不會(huì)造成循環(huán)引用。因?yàn)樵诜椒ㄕ{(diào)用完后,runloop會(huì)解除對(duì)timer的引用,timer會(huì)解除對(duì)target的引用
    NSTimer *aTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    // 1、定時(shí)器只運(yùn)行在NSDefaultRunLoopMode下,一旦RunLoop進(jìn)入其他模式,這個(gè)定時(shí)器就不會(huì)工作
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    // 2、定時(shí)器只運(yùn)行在UITrackingRunLoopMode下(滑動(dòng)UIScrollView),一旦RunLoop進(jìn)入其他模式,這個(gè)定時(shí)器就不會(huì)工作
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // 3、標(biāo)記為NSRunLoopCommonModes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode兼容
    [[NSRunLoop mainRunLoop] addTimer:aTimer forMode:NSRunLoopCommonModes];
}
scheduledTimerWithTimeInterval:方法

(1)自動(dòng)加入當(dāng)前RunLoop,模式是NSDefaultRunLoopMode
(2)創(chuàng)建的timer時(shí)不需要額外開啟的,但注意:它不能保證一進(jìn)runloop就立即調(diào)用,fire方法可以做到。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //主線程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        // 這種方法是直接加入RunLoop:[[NSRunLoop currentRunLoop] addTimer: t forMode: NSDefaultRunLoopMode];
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerFired:)userInfo:nil repeats:YES];
        // 需要確保當(dāng)前currentRunLoop run起來(lái)
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
    
}

RunLoop的run與stop

1、CFRunLoopStop能直接停止掉所有用CFRunloop運(yùn)行起runloop
2、NSRunloop是基于CFRunloop來(lái)封裝的,NSRunloop是線程不安全的,而CFRunloop則是線程安全的

- (void)testTimer
{
    NSTimer *aTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(runrun) userInfo:nil repeats:YES];
    // 手動(dòng)獲取
    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
    // 由于UI都是在主線程,子線程不會(huì)有UITrackingRunLoopMode干擾,所以使用NSDefaultRunLoopMode就可以了
    [currentRunLoop addTimer:aTimer forMode:NSDefaultRunLoopMode];
    // run起來(lái)的方式,有
    // 方式1
    // 方式2
    // 方式3
}

方式1:run

    /*方式1:
     永久性的運(yùn)行在NSDefaultRunLoopMode模式
     停止runloop方式:
     1、使用CFRunLoopStop無(wú)效
     2、(1)停止移除timer或者移除port;(2)暴力強(qiáng)制退出線程(不是解決辦法)
     */
    [currentRunLoop run];

方式2:runUntilDate

    /*方式2:
     規(guī)定時(shí)間運(yùn)行在NSDefaultRunLoopMode模式
     停止runloop方式:
     1、使用CFRunLoopStop無(wú)效
     2、(1)停止移除timer或者移除port;(2)到達(dá)指定時(shí)間
     */
    [currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];//

方式3:runMode: beforeDate: (其實(shí)調(diào)用CFRunLoopRunInMode)

    // returnAfterSourceHandled參數(shù)為YES,當(dāng)觸發(fā)一個(gè)非timer事件后,runloop就終止了
    CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);returnAfterSourceHandled = YES的封裝;
    /*方式3:
     規(guī)定runMode、規(guī)定時(shí)間,運(yùn)行完返回運(yùn)行狀態(tài)
     停止runloop方式:
        (1)使用CFRunLoopStop;
        (2)停止移除timer或者移除port;
        (3)到達(dá)指定時(shí)間
     */

    BOOL result =  [currentRunLoop runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:20]];
    if (result) {
        // 如果是PerfromSelector*事件或者其他Input Source事件觸發(fā)處理后,Run Loop結(jié)束時(shí)候返回YES,其他返回NO。
    }else {
        // NO
    }

}

一定時(shí)間內(nèi)監(jiān)聽某種事件,或執(zhí)行某種任務(wù)的線程,如在30分鐘內(nèi),每隔30s執(zhí)行onTimerFired:

@autoreleasepool {
    
    NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
                                                    target:self
                                                  selector:@selector(onTimerFired:)
                                                  userInfo:nil
                                                   repeats:YES];

    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}

CADisplayLink與NSTimer的區(qū)別

CADisplayLink 是一個(gè)和屏幕刷新率一致的定時(shí)器(但實(shí)際實(shí)現(xiàn)原理更復(fù)雜,和 NSTimer 并不一樣,其內(nèi)部實(shí)際是操作了一個(gè) Source1)。如果在兩次屏幕刷新之間執(zhí)行了一個(gè)長(zhǎng)任務(wù),那其中就會(huì)有一幀被跳過(guò)去(和 NSTimer 相似),造成界面卡頓的感覺(jué)。在快速滑動(dòng)TableView時(shí),即使一幀的卡頓也會(huì)讓用戶有所察覺(jué)。Facebook 開源的 AsyncDisplayLink 就是為了解決界面卡頓的問(wèn)題,其內(nèi)部也用到了 RunLoop

1、 NSTimer是需要在上一次RunLoop整個(gè)完成之后才會(huì)調(diào)用制定的selector
2、CADisplayLink是Source1,主動(dòng)喚醒 RunLoop。屏幕需要進(jìn)行重繪時(shí)就會(huì)喚醒RunLoop調(diào)用CADisplayLink指定的selector,用于準(zhǔn)備下一幀顯示的數(shù)據(jù)

2、performSelector: withObject: afterDelay: inModes (內(nèi)部創(chuàng)建一個(gè)NSTimer加入當(dāng)前RunLoop)

主線程

    // 延時(shí)performSelector在主線程會(huì)被RunLoopMode干擾
    // 延時(shí)performSelector其實(shí)里面使用NSTimer來(lái)實(shí)現(xiàn)afterDelay
    [self performSelector:@selector(performSelector) withObject:nil afterDelay:2   inModes:@[NSDefaultRunLoopMode]];

子線程

    BBThread *thread = [[BBThread alloc] initWithBlock:^{
        
        [self performSelector:@selector(performSelector) withObject:nil afterDelay:2 inModes:@[NSDefaultRunLoopMode]];
        // 需要手動(dòng)currentRunLoop跑起來(lái)
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        [currentRunLoop run];
         
    }];
    [thread start];
3、常駐線程

AFNetworking 2.0中 創(chuàng)建了一條常駐線程專門處理所有請(qǐng)求的回調(diào)事件

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         // 這里主要是監(jiān)聽某個(gè) port,目的是讓這個(gè) Thread 不會(huì)回收
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread =
        [[NSThread alloc] initWithTarget:self
                                selector:@selector(networkRequestThreadEntryPoint:)
                                  object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
4、線程、RunLoop、AutoreleasePool三者關(guān)系
(1)線程與RunLoop:一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop,懶加載形式。

1、線程和 RunLoop 之間是一一對(duì)應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。
2、線程剛創(chuàng)建時(shí)并沒(méi)有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有。
3、RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。

CFRunLoopGetMain() 獲取主線程的RunLoop
CFRunLoopGetCurrent() 獲取當(dāng)前線程的RunLoop

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問(wèn) loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;
 
/// 獲取一個(gè) pthread 對(duì)應(yīng)的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);
    
    if (!loopsDic) {
        // 第一次進(jìn)入時(shí),初始化全局Dic,并先為主線程創(chuàng)建一個(gè) RunLoop。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }
    
    /// 直接從 Dictionary 里獲取。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
    
    if (!loop) {
        /// 取不到時(shí),創(chuàng)建一個(gè)
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        /// 注冊(cè)一個(gè)回調(diào),當(dāng)線程銷毀時(shí),順便也銷毀其對(duì)應(yīng)的 RunLoop。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }
    
    OSSpinLockUnLock(&loopsLock);
    return loop;
}
 
CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}
 
CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

從上面的代碼可以看出,線程和 RunLoop 之間是一一對(duì)應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。線程剛創(chuàng)建時(shí)并沒(méi)有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)。

AutoreleasePool與RunLoop

1、一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop (主線程系統(tǒng)啟動(dòng),其他線程需要使用來(lái)加載獲取并啟動(dòng))
2、線程如果沒(méi)有啟動(dòng)RunLoop,遇到autorelease對(duì)象會(huì)用下面情況:

1)、線程里面如果有對(duì)象調(diào)用autorelease方法,系統(tǒng)就去找這個(gè)線程key對(duì)應(yīng)的最棧頂poolpage;
2)、如果找到page.add(obj)OK;
3)、如果找不到就調(diào)用autoreleaseNoPage,新建這個(gè)線程的一個(gè)page;
4)、線程釋放,這個(gè)線程對(duì)應(yīng)的page也pop。

3、線程啟動(dòng)了RunLoop,RunLoop內(nèi)部會(huì)管理AutoreleasePool

1)、RunLoop開啟時(shí)會(huì)objc_autoreleasePoolPush;
2)、RunLoop休眠時(shí)會(huì)先objc_autoreleasePoolPop再objc_autoreleasePoolPush;
3)、RunLoop退出時(shí)會(huì)objc_autoreleasePoolPop;

4、另外
在啟動(dòng)RunLoop之前建議用 @autoreleasepool {...}包裹
意義:創(chuàng)建一個(gè)大釋放池,釋放{}期間創(chuàng)建的臨時(shí)對(duì)象,一般好的框架的作者都會(huì)這么做 (上面的AFNetworking就是這么做)

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         // 這里主要是監(jiān)聽某個(gè) port,目的是讓這個(gè) Thread 不會(huì)回收
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 
        [runLoop run];
    }
}
NSRunLoop與NSMachPort來(lái)線程通信的實(shí)例:
- (void)testDemo3
{
    //聲明兩個(gè)端口   隨便怎么寫創(chuàng)建方法,返回的總是一個(gè)NSMachPort實(shí)例
    NSMachPort *mainPort = [[NSMachPort alloc]init];
    NSPort *threadPort = [NSMachPort port];
    //設(shè)置線程的端口的代理回調(diào)為自己
    threadPort.delegate = self;

    //給主線程runloop加一個(gè)端口
    [[NSRunLoop currentRunLoop]addPort:mainPort forMode:NSDefaultRunLoopMode];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        //添加一個(gè)Port
        [[NSRunLoop currentRunLoop]addPort:threadPort forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

    });

    NSString *s1 = @"hello";

    NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSMutableArray *array = [NSMutableArray arrayWithArray:@[mainPort,data]];
        //過(guò)2秒向threadPort發(fā)送一條消息,第一個(gè)參數(shù):發(fā)送時(shí)間。msgid 消息標(biāo)識(shí)。
        //components,發(fā)送消息附帶參數(shù)。reserved:為頭部預(yù)留的字節(jié)數(shù)(從官方文檔上看到的,猜測(cè)可能是類似請(qǐng)求頭的東西...)
        [threadPort sendBeforeDate:[NSDate date] msgid:1000 components:array from:mainPort reserved:0];

    });

}

//這個(gè)NSMachPort收到消息的回調(diào),注意這個(gè)參數(shù),可以先給一個(gè)id。如果用文檔里的NSPortMessage會(huì)發(fā)現(xiàn)無(wú)法取值
- (void)handlePortMessage:(id)message
{

    NSLog(@"收到消息了,線程為:%@",[NSThread currentThread]);

    //只能用KVC的方式取值
    NSArray *array = [message valueForKeyPath:@"components"];

    NSData *data =  array[1];
    NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",s1);

//    NSMachPort *localPort = [message valueForKeyPath:@"localPort"];
//    NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];

}

打印如下:

2016-11-23 16:50:20.604 TestRunloop3[1322:120162] 收到消息了,線程為:<NSThread: 0x60800026d700>{number = 3, name = (null)}
2016-11-23 16:50:26.551 TestRunloop3[1322:120162] hello

自定義的輸入源來(lái)實(shí)現(xiàn)線程通信
- (void)testDemo4
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"starting thread.......");

        _runLoopRef = CFRunLoopGetCurrent();
        //初始化_source_context。
        bzero(&_source_context, sizeof(_source_context));
        //這里創(chuàng)建了一個(gè)基于事件的源,綁定了一個(gè)函數(shù)
        _source_context.perform = fire;
        //參數(shù)
        _source_context.info = "hello";
        //創(chuàng)建一個(gè)source
        _source = CFRunLoopSourceCreate(NULL, 0, &_source_context);
        //將source添加到當(dāng)前RunLoop中去
        CFRunLoopAddSource(_runLoopRef, _source, kCFRunLoopDefaultMode);

        //開啟runloop 第三個(gè)參數(shù)設(shè)置為YES,執(zhí)行完一次事件后返回
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 9999999, YES);

        NSLog(@"end thread.......");
    });


    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        if (CFRunLoopIsWaiting(_runLoopRef)) {
            NSLog(@"RunLoop 正在等待事件輸入");
            //添加輸入事件
            CFRunLoopSourceSignal(_source);
            //喚醒線程,線程喚醒后發(fā)現(xiàn)由事件需要處理,于是立即處理事件
            CFRunLoopWakeUp(_runLoopRef);
        }else {
            NSLog(@"RunLoop 正在處理事件");
            //添加輸入事件,當(dāng)前正在處理一個(gè)事件,當(dāng)前事件處理完成后,立即處理當(dāng)前新輸入的事件
            CFRunLoopSourceSignal(_source);
        }
    });

}

//此輸入源需要處理的后臺(tái)事件
static void fire(void* info){

    NSLog(@"我現(xiàn)在正在處理后臺(tái)任務(wù)");

    printf("%s",info);
}

輸出結(jié)果如下:

2016-11-24 10:42:24.045 TestRunloop3[4683:238183] starting thread.......
2016-11-24 10:42:26.045 TestRunloop3[4683:238082] RunLoop 正在等待事件輸入 
2016-11-24 10:42:31.663 TestRunloop3[4683:238183] 我現(xiàn)在正在處理后臺(tái)任務(wù)
hello
2016-11-24 10:42:31.663 TestRunloop3[4683:238183] end thread.......

CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變

RunLoop的狀態(tài)

     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),   //   即將進(jìn)入RunLoop
     kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
     kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
     kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
     kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
     kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     };
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //創(chuàng)建監(jiān)聽者
    /*
     第一個(gè)參數(shù) CFAllocatorRef allocator:分配存儲(chǔ)空間 CFAllocatorGetDefault()默認(rèn)分配
     第二個(gè)參數(shù) CFOptionFlags activities:要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽所有狀態(tài)
     第三個(gè)參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽 NO:不持續(xù)
     第四個(gè)參數(shù) CFIndex order:優(yōu)先級(jí),一般填0即可
     第五個(gè)參數(shù) :回調(diào) 兩個(gè)參數(shù)observer:監(jiān)聽者 activity:監(jiān)聽的事件
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop進(jìn)入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要處理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要處理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒來(lái)了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
                
            default:
                break;
        }
    });
    
    // 給RunLoop添加監(jiān)聽者
    /*
     第一個(gè)參數(shù) CFRunLoopRef rl:要監(jiān)聽哪個(gè)RunLoop,這里監(jiān)聽的是主線程的RunLoop
     第二個(gè)參數(shù) CFRunLoopObserverRef observer 監(jiān)聽者
     第三個(gè)參數(shù) CFStringRef mode 要監(jiān)聽RunLoop在哪種運(yùn)行模式下的狀態(tài)
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    /*
     CF的內(nèi)存管理(Core Foundation)
     凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來(lái)的對(duì)象,都需要在最后做一次release
     GCD本來(lái)在iOS6.0之前也是需要我們釋放的,6.0之后GCD已經(jīng)納入到了ARC中,所以我們不需要管了
     */
    CFRelease(observer);
}


CFRunLoopObserverRef使用demo:
利用主線程RunLoop空閑時(shí)候在處理,一些UI事情,減少卡頓(這就不需要多線程了)

 CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        if (activity == kCFRunLoopBeforeWaiting) {
            // RunLoop要休息了
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);
RunLoop與GCD

dispatch_async(dispatch_get_main_queue(), block) 時(shí),libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息,RunLoop會(huì)被喚醒,并從消息中取得這個(gè) block,并在回調(diào) CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里執(zhí)行這個(gè) block。(這個(gè)邏輯僅限于 dispatch 到主線程,dispatch 到其他線程仍然是由 libDispatch 處理的)

NSNotificationQueue也與runloop有關(guān)系OC--NSNotificationCenter重新認(rèn)知

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ======================= 前言 RunLoop 是 iOS 和 OSX 開發(fā)中非?;A(chǔ)的一個(gè)...
    i憬銘閱讀 985評(píng)論 0 4
  • 一、什么是runloop 字面意思是“消息循環(huán)、運(yùn)行循環(huán)”。它不是線程,但它和線程息息相關(guān)。一般來(lái)講,一個(gè)線程一次...
    WeiHing閱讀 8,300評(píng)論 11 111
  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的,也是非常重要的, 在面試過(guò)程中是經(jīng)常會(huì)被問(wèn)到的, ...
    made_China閱讀 1,269評(píng)論 0 7
  • Run loop 剖析:Runloop 接收的輸入事件來(lái)自兩種不同的源:輸入源(intput source)和定時(shí)...
    Mitchell閱讀 12,629評(píng)論 17 111
  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的,也是非常重要的, 在面試過(guò)程中是經(jīng)常會(huì)被問(wèn)到的, ...
    SOI閱讀 22,009評(píng)論 3 63

友情鏈接更多精彩內(nèi)容