【轉(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。
看文檔中對(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
。
官方注釋:
那按照官方文檔說(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ì)比:
有了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)了。
觀察者打印:
可以看出,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)入休眠。
我在子線程中加入了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