自己平常开发中比较少用到performSelector
相关的API,但是平常看些第三方的时候,发现第三方作者用到performSelector
相关的API比较多。自己理解的是,可以在一定程度上解耦,不必引入相关类。但是最近在用到时,遇到了一些问题。由此,查看了一些博客,自己也做了验证,在此记录一下。
先看一段代码:
1 | 复制代码- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event |
执行结果如下:没有打印出2,只打印出了1和3。
看文档中对这个API的注释是说,这个方法调用后,在当前runloop里设置了一个timer,来触发这个方法执行。而当前这个方法是在子线程中调用的,在子线程中runloop不是自动创建并跑起来的,需要手动调用,才会创建。因为这个在子线程中的调用没有创建runloop,所以就没有执行testPerform
。
官方注释:
那按照官方文档说明在子线程中加入runloop,看下执行效果。
1 | 复制代码- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event |
通过获取当前的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观察者的代码:
1 | 复制代码- (void)addObserver |
本篇记录算是自己的理解,水平有限,如果有错误的地方,请批评指正,会尽快修改。
参考致谢:
本文转载自: 掘金