美文网首页iOS开发宝典
performSelector: withObject: aft

performSelector: withObject: aft

作者: 不羁的躁动 | 来源:发表于2019-06-26 11:38 被阅读0次

    上一次仓促面试,给了一份面试题让我去做,题目的主要内容就是多线程的相关知识。其中有一题是这样的:

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"这是1");
            [self performSelector:@selector(test) withObject:nil afterDelay:0];
            NSLog(@"这是3");
        });
    }
    
    -(void)test{
        NSLog(@"这是2");
    }
    

    让我写出程序的打印顺序。我写了1,3,2,结果不用说是错了,应该是1,3。然后回来就立马去查询相关资料,才知道错误原因。

    首先我查看了这个方法的官方API,如图: 屏幕快照 2019-06-25 下午6.10.40.png
    API上面说的很清楚,该方法会在当前线程的runloop中添加定时器,但是我们使用的是异步执行+全局并发,就会开启子线程执行block中的任务,这个要是不知道,那就去补一下GCD相关知识。

    performSelector:withObject:afterDelay: 的底层

    • 不在 NSObject 中,而是在NSRunLoop 类中
    • 带有 afterDelay 的方法,都是在 NSRunLoop 类中定义的

    为何在 主线程就可以调用 test 方法,在GCD 中却不能调用 test 方法?

    • 这是因为 performSelector:withObject:afterDelay: 类的底层调用了 NSTimer 定时器。
    • 定时器是要添加到 runloop 中去的。
    • 这句话的底层就是 往 runloop 中添加了一个定时器。
    • 主线程 默认有 runloop,所以 在主线程 可以调用 test方法。
    • 而子线程中没有 runloop ,所以 不会调用 test 方法。如果想要在 GCD 中调用 test 方法,需要自己开启 runloop 。

    那么问题来了,怎么让它执行呢,有几个方法,下面一一介绍:

    方法一:

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

    既然我们是afterDelay是0秒之后,那么我们就稍微修改一下,用跟它很相近的方法:

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

    该方法跟上面方法最大的区别就是不再使用定时器,而是直接执行,这样就不存在Runloop启动不启动的问题了。修改后如下:

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"这是1");
            [self performSelector:@selector(test) withObject:nil];
            NSLog(@"这是3");
        });
    }
    
    -(void)test{
        NSLog(@"这是2");
    }
    

    执行结果是:

    2019-06-25 18:32:20.066710+0800 GCDTest[19808:1137360] 这是1
    2019-06-25 18:32:20.066870+0800 GCDTest[19808:1137360] 这是2
    2019-06-25 18:32:20.066973+0800 GCDTest[19808:1137360] 这是3
    

    是不是很方便。

    方法二

    既然上面说到子线程中Runloop没有启动,那就给它一个Runloop让它启动。具体实现是:

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"这是1");
            [self performSelector:@selector(test) withObject:nil afterDelay:0];
            NSLog(@"这是3");
            //子线程开启Runloop,注意该方法要写在performSelector方法之后才有效
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        });
    }
    
    -(void)test{
        //子线程关闭Runloop
        CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
        NSLog(@"这是2");
    }
    

    打印结果是:

    2019-06-26 11:28:31.507646+0800 GCDTest[6848:233750] 这是1
    2019-06-26 11:28:31.507919+0800 GCDTest[6848:233750] 这是3
    2019-06-26 11:28:31.508048+0800 GCDTest[6848:233750] 这是2
    

    如果我们此时在test方法中再执行一个gcd方法,就不会执行,如:

    -(void)test{
        //子线程关闭Runloop
        CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
        NSLog(@"这是2");
    
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            [self performSelector:@selector(test2) withObject:nil afterDelay:1];
        });
    }
    
    -(void)test2{
        NSLog(@"不会执行");
    }
    

    打印结果还是:

    2019-06-26 11:29:26.430061+0800 GCDTest[6864:237962] 这是1
    2019-06-26 11:29:26.430325+0800 GCDTest[6864:237962] 这是3
    2019-06-26 11:29:26.430482+0800 GCDTest[6864:237962] 这是2
    

    这是因为子线程中的runloop已经被关闭了。

    方法三

    既然我们说主线程的runloop是默认开启的,子线程的没有开启,那么我们就把该方法放到主队列中执行就可以了。代码如下:

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"这是1");
            [self performSelector:@selector(test) withObject:nil afterDelay:0];
            NSLog(@"这是3");
        });
    }
    
    -(void)test{
        NSLog(@"这是2");
    }
    
    -(void)test2{
        NSLog(@"不会执行");
    }
    

    执行结果如下:

    2019-06-26 11:33:29.554860+0800 GCDTest[6896:247259] 这是1
    2019-06-26 11:33:29.555022+0800 GCDTest[6896:247259] 这是3
    2019-06-26 11:33:29.555248+0800 GCDTest[6896:247259] 这是2
    

    是不是完美解决了这个问题。
    好了,觉得对你有帮助的话记得动手点个赞呦,关注我,会给你带来更多关于iOS底层的相关知识。

    相关文章

      网友评论

        本文标题:performSelector: withObject: aft

        本文链接:https://www.haomeiwen.com/subject/rsvxcctx.html