iOS开发宝典

performSelector: withObject: aft

2019-06-26  本文已影响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: 的底层

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

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

方法一:

[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底层的相关知识。

上一篇下一篇

猜你喜欢

热点阅读