关于performSelector: withObject: afterDelay:的坑

1,350 阅读2分钟

先看一段代码:

- (void)testPrint {
    NSLog(@"3--%@",[NSThread currentThread]);
}
- (void)testPrint2 {
    NSLog(@"4--%@",[NSThread currentThread]);
}
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"1--%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        [self performSelector:@selector(testPrint) withObject:nil afterDelay:0];//3
        [self performSelector:@selector(testPrint2) withObject:nil];//4
    });
    NSLog(@"2--%@",[NSThread currentThread]);
}

打印结果如下: image.png

dispatch_sync改为dispatch_async,打印如下: image.png

为什么performSelector: withObject: afterDelay:打印是这样的呢? 下面是苹果API的注释: image.png 汤师爷翻译翻译如下:

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

 在延迟后使用默认模式在当前线程上调用接收方的方法。
此方法设置一个计时器,在当前线程的运行循环中执行aSelector消息。计时器配置为在默认模式(NSDefaultRunLoopMode)下运行。当计时器触发时,线程尝试将消息从运行循环中出列并执行选择器。如果运行循环正在运行且处于默认模式,则会成功;否则,计时器将等待运行循环处于默认模式。

 如果希望在运行循环处于默认模式以外的模式时将消息退出队列,请改用performSelect:withObject:afterDelay:inModes:方法。如果不确定当前线程是否为主线程,可以使用performSelectorOnMainThread:withObject:waitUntilDone:performSelectorOnMainThread:withObject:waitUntilDone:modes:方法来确保选择器在主线程上执行。要取消排队的消息,请使用cancelPreviousPerformRequestsWithTarget:cancelPreviousPerformRequestsWithTarget:selector:object: method

 afterDelay:发送消息的最短时间。指定延迟0不一定会立即执行选择器。选择器仍然在线程的运行循环中排队,并尽快执行。

总结

  • dispatch_sync时,- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay会等待下次runloop循环调用
  • dispatch_async时,异步全局并发会创建子线程,子线程默认不开启runloop,所以此testPrint未调用

改进

想要执行testPrint方法,官方文档也提供了解决办法:

[self performSelectorOnMainThread:@selector(testPrint) withObject:nil waitUntilDone:YES];

其实针对上述的逻辑,更简单的是:

[self performSelector:@selector(testPrint) withObject:nil];

如果没有参数,可以再简单些:

[self performSelector:@selector(testPrint)];

思考

  • 其实我们常用的perform是NSObject.h这个头文件的方法;
    - (id)performSelector:(SEL)aSelector withObject:(id)object;
  • 可以delay的,是NSRunLoop.h下的方法;
    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
  • 而之前提到的回调主线程的,是NSThread.h里的方法:
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

虽然他们都是NSObject的方法或者是分类补充方法,但实际上,是隶属于不同模块的;

对比上面三个方法,后面两个是没有返回值的。

补充

NSObject的performSelector方法源码如下: image.png

关于NSRunLoop和NSThread的源码,现在xcode打开源码闪退,等后续补充。。

引用