关于 performSelector:afterDelay: 的一个坑及思考

转自【知识小集:霖溦】

先看一段代码

-(void)viewDidLoad{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
        NSLog(@"2");
    });
}
- (void)test{
    NSLog(@"3");
}

这段代码的执行结果是1、2

原因

因为- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay实际在runloop里面,是一个定时器,但是因为子线程,runloop是默认没有开启的

下面是苹果API的注释,就能解释这个问题:


image.png

想要执行-(void)test方法,官方文档也提供了解决办法:

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

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

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

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

[self performSelector:@selector(test)];

思考

  • 其实我们常用的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的方法或者是分类补充方法,但实际上,是隶属于不同模块的;

对比上面三个方法,后面两个是没有返回值的。这其实也是有官方注释的依据的:


image.png
image.png

我们很多人应该总是会被上述的警告所困扰,大多数人的解决方式,就是利用类似下面的方式去屏蔽警告,这种做法虽然简单,但实际是有风险的:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//code
#pragma clang diagnostic pop

其实除了利用 IMP 或者 NSInvocation 那种比较“高端”的方式,更多的情况下,在方法没有返回值时,或者我们不需要返回值时,我们可以用:

[self performSelector:@selector(test) withObject:nil afterDelay:0];

所以你比如在YYText方法里也能看到[self performSelector:@selector(test) withObject:nil afterDelay:0];这种调用方式,不能简单的认为是犯了低级错误,当我们不需要返回值的时候,按照官方文档的建议,我们可以用performSelectorOnMainThread,也可以用[self performSelector:@selector(test) withObject:nil afterDelay:0];,当然官方的是最严谨的,没有使用delay的原因,就是因为多线程runloop的问题了

你可能感兴趣的:(关于 performSelector:afterDelay: 的一个坑及思考)