Runloop与performSelector

自己平常开发中比较少用到performSelector相关的API,但是平常看些第三方的时候,发现第三方作者用到performSelector相关的API比较多。自己理解的是,可以在一定程度上解耦,不必引入相关类。但是最近在用到时,遇到了一些问题。由此,查看了一些博客,自己也做了验证,在此记录一下。

先看一段代码:

- (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");
}
复制代码

执行结果如下:没有打印出2,只打印出了1和3。

结果
看文档中对这个API的注释是说,这个方法调用后,在当前runloop里设置了一个timer,来触发这个方法执行。而当前这个方法是在子线程中调用的,在子线程中runloop不是自动创建并跑起来的,需要手动调用,才会创建。因为这个在子线程中的调用没有创建runloop,所以就没有执行 testPerform

官方注释:

那按照官方文档说明在子线程中加入runloop,看下执行效果。

- (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");
    });
}
复制代码

通过获取当前的runloop,系统就会返回当前的runloop,如果没有的话,会创建后返回,但是加入了runloop的时候,执行结果,依然是只有打印出来1和3,没有打印2。在[self performSelector:@selector(testPerform) withObject:nil afterDelay:0]方法调用前后,通过控制台打印runloop对象,确实看到了调用方法后,runloop里多了一个timer源。

前后runloop对比:

有了runloop也有了触发方法 testPerform执行的timer,为什么还依然没有执行。因为runloop没有跑起来。 所以创建完runloop后,还需要runloop跑起来。【通过给当前runloop添加观察者,查看runloop的状态,runloop没有跑起来】当我们调用 [runloop run];方法后,将runloop跑起来后, testPerform才会执行。打印结果为1,2,3。

但,问题又来了,既然加入了runloop,并且跑起来了,为什么3还会打印出来,runloop不是相当于死循环吗?循环外的3为什么会打印出来?这个问题,通过加入的runloop的观察者的打印情况可以看出来,是因为,runloop在执行完testPerform后,就退出了。所以下边的3页打印出来了。

观察者打印:

可以看出,3是在runloop退出后,打印出来的。【在 testPerform方法内打印runloop,看到此时runloop对象的timers数组里边已经是空的了。runloop的mode里没有source1、没有source0、也没有timer源,所以就退出了】由此,也可以猜测:在runloop里设置的timer触发 [self performSelector:@selector(testPerform) withObject:nil afterDelay:0]方法后,该timer就销毁了。

怎样让runloop不退出呢?给当前runloop加入事件源或定时器temers,当前runloop就不会退出了,只是在不需要执行任务的时候进入休眠。

我在子线程中加入了timer后,通过观察者的打印结果来看,该runloop一直没有退出,所以3也就没有打印出来。【注意, repeats参数要设置为YES,否则执行完timer之后,runloop就不再持有timer,runloop就退出来了。还可以通过 [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];加入事件源的方法,使runloop一直不退出。】

还有一个方法是- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;这个方法多了一个设置mode的参数,可以通过这个参数设置在timer在哪个mode下执行,读者可自己检测。

添加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(@"进入runloop");
            }
                break;
            case 2:
            {
                NSLog(@"timers");
            }
                break;
            case 4:
            {
                NSLog(@"sources");
            }
                break;
            case 32:
            {
                NSLog(@"即将进入休眠");
            }
                break;
            case 64:
            {
                NSLog(@"唤醒");
            }
                break;
            case 128:
            {
                NSLog(@"退出");
            }
                break;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);//将观察者添加到common模式下,这样当default模式和UITrackingRunLoopMode两种模式下都有回调。
    self.obsever  = observer;
    CFRelease(observer);
}

复制代码

本篇记录算是自己的理解,水平有限,如果有错误的地方,请批评指正,会尽快修改。


参考致谢:

iOS底层原理总结 - RunLoop

关于 performSelector 的一些小探讨

NSRunLoop的退出方式

猜你喜欢

转载自juejin.im/post/5c70b391e51d451646267db1