Runloop與performSelector探究

【轉(zhuǎn)載自:https://juejin.im/post/5c70b391e51d451646267db1

自己平常開(kāi)發(fā)中比較少用到performSelector相關(guān)的API,但是平常看些第三方的時(shí)候,發(fā)現(xiàn)第三方作者用到performSelector相關(guān)的API比較多。自己理解的是,可以在一定程度上解耦,不必引入相關(guān)類。但是最近在用到時(shí),遇到了一些問(wèn)題。由此,查看了一些博客,自己也做了驗(yàn)證,在此記錄一下。

先看一段代碼:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(testPerform) withObject:nil afterDelay:0];//
        NSLog(@"3");
    });
}
- (void)testPerform{
    NSLog(@"2");
}
復(fù)制代碼

執(zhí)行結(jié)果如下:沒(méi)有打印出2,只打印出了1和3。

image.png

看文檔中對(duì)這個(gè)API的注釋是說(shuō),這個(gè)方法調(diào)用后,在當(dāng)前runloop里設(shè)置了一個(gè)timer,來(lái)觸發(fā)這個(gè)方法執(zhí)行。而當(dāng)前這個(gè)方法是在子線程中調(diào)用的,在子線程中runloop不是自動(dòng)創(chuàng)建并跑起來(lái)的,需要手動(dòng)調(diào)用,才會(huì)創(chuàng)建。因?yàn)檫@個(gè)在子線程中的調(diào)用沒(méi)有創(chuàng)建runloop,所以就沒(méi)有執(zhí)行testPerform

官方注釋:

image.png

那按照官方文檔說(shuō)明在子線程中加入runloop,看下執(zhí)行效果。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"1");
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [self performSelector:@selector(testPerform) withObject:nil afterDelay:0];//
        NSLog(@"3");
    });
}
復(fù)制代碼

通過(guò)獲取當(dāng)前的runloop,系統(tǒng)就會(huì)返回當(dāng)前的runloop,如果沒(méi)有的話,會(huì)創(chuàng)建后返回,但是加入了runloop的時(shí)候,執(zhí)行結(jié)果,依然是只有打印出來(lái)1和3,沒(méi)有打印2。在[self performSelector:@selector(testPerform) withObject:nil afterDelay:0]方法調(diào)用前后,通過(guò)控制臺(tái)打印runloop對(duì)象,確實(shí)看到了調(diào)用方法后,runloop里多了一個(gè)timer源。

前后runloop對(duì)比:

image.png

有了runloop也有了觸發(fā)方法testPerform執(zhí)行的timer,為什么還依然沒(méi)有執(zhí)行。因?yàn)閞unloop沒(méi)有跑起來(lái)。 所以創(chuàng)建完runloop后,還需要runloop跑起來(lái)。【通過(guò)給當(dāng)前runloop添加觀察者,查看runloop的狀態(tài),runloop沒(méi)有跑起來(lái)】當(dāng)我們調(diào)用[runloop run];方法后,將runloop跑起來(lái)后,testPerform才會(huì)執(zhí)行。打印結(jié)果為1,2,3。

但,問(wèn)題又來(lái)了,既然加入了runloop,并且跑起來(lái)了,為什么3還會(huì)打印出來(lái),runloop不是相當(dāng)于死循環(huán)嗎?循環(huán)外的3為什么會(huì)打印出來(lái)?這個(gè)問(wèn)題,通過(guò)加入的runloop的觀察者的打印情況可以看出來(lái),是因?yàn)椋瑀unloop在執(zhí)行完testPerform后,就退出了。所以下邊的3頁(yè)打印出來(lái)了。

觀察者打印:

image.png

可以看出,3是在runloop退出后,打印出來(lái)的。【在testPerform方法內(nèi)打印runloop,看到此時(shí)runloop對(duì)象的timers數(shù)組里邊已經(jīng)是空的了。runloop的mode里沒(méi)有source1、沒(méi)有source0、也沒(méi)有timer源,所以就退出了】由此,也可以猜測(cè):在runloop里設(shè)置的timer觸發(fā)[self performSelector:@selector(testPerform) withObject:nil afterDelay:0]方法后,該timer就銷毀了。

怎樣讓runloop不退出呢?給當(dāng)前runloop加入事件源或定時(shí)器temers,當(dāng)前runloop就不會(huì)退出了,只是在不需要執(zhí)行任務(wù)的時(shí)候進(jìn)入休眠。

image.png

我在子線程中加入了timer后,通過(guò)觀察者的打印結(jié)果來(lái)看,該runloop一直沒(méi)有退出,所以3也就沒(méi)有打印出來(lái)。【注意,repeats參數(shù)要設(shè)置為YES,否則執(zhí)行完timer之后,runloop就不再持有timer,runloop就退出來(lái)了。還可以通過(guò)[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];加入事件源的方法,使runloop一直不退出。】

還有一個(gè)方法是- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;這個(gè)方法多了一個(gè)設(shè)置mode的參數(shù),可以通過(guò)這個(gè)參數(shù)設(shè)置在timer在哪個(gè)mode下執(zhí)行,讀者可自己檢測(cè)。

添加runloop觀察者的代碼:

- (void)addObserver
{
    /*
     kCFRunLoopEntry = (1UL << 0),1
     kCFRunLoopBeforeTimers = (1UL << 1),2
     kCFRunLoopBeforeSources = (1UL << 2), 4
     kCFRunLoopBeforeWaiting = (1UL << 5), 32
     kCFRunLoopAfterWaiting = (1UL << 6), 64
     kCFRunLoopExit = (1UL << 7),128
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case 1:
            {
                NSLog(@"進(jìn)入runloop");
            }
                break;
            case 2:
            {
                NSLog(@"timers");
            }
                break;
            case 4:
            {
                NSLog(@"sources");
            }
                break;
            case 32:
            {
                NSLog(@"即將進(jìn)入休眠");
            }
                break;
            case 64:
            {
                NSLog(@"喚醒");
            }
                break;
            case 128:
            {
                NSLog(@"退出");
            }
                break;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);//將觀察者添加到common模式下,這樣當(dāng)default模式和UITrackingRunLoopMode兩種模式下都有回調(diào)。
    self.obsever  = observer;
    CFRelease(observer);
}

作者:一修Grace
鏈接:https://juejin.im/post/5c70b391e51d451646267db1

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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