Runloop与performSelector

栏目: IOS · 发布时间: 5年前

内容简介:自己平常开发中比较少用到先看一段代码:执行结果如下:没有打印出2,只打印出了1和3。

自己平常开发中比较少用到 performSelector 相关的API,但是平常看些第三方的时候,发现第三方作者用到 performSelector 相关的API比较多。自己理解的是,可以在一定程度上解耦,不必引入相关类。但是最近在用到时,遇到了一些问题。由此,查看了一些博客,自己也做了验证,在此记录一下。

先看一段代码:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(testPerform) withObject:nil afterDelay:0];//
        NSLog(@"3");
    });
}
- (void)testPerform{
    NSLog(@"2");
}
复制代码

执行结果如下:没有打印出2,只打印出了1和3。

Runloop与performSelector
看文档中对这个API的注释是说,这个方法调用后,在当前runloop里设置了一个timer,来触发这个方法执行。而当前这个方法是在子线程中调用的,在子线程中runloop不是自动创建并跑起来的,需要手动调用,才会创建。因为这个在子线程中的调用没有创建runloop,所以就没有执行 testPerform

官方注释:

Runloop与performSelector

那按照官方文档说明在子线程中加入runloop,看下执行效果。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"1");
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [self performSelector:@selector(testPerform) withObject:nil afterDelay:0];//
        NSLog(@"3");
    });
}
复制代码

通过获取当前的runloop,系统就会返回当前的runloop,如果没有的话,会创建后返回,但是加入了runloop的时候,执行结果,依然是只有打印出来1和3,没有打印2。在 [self performSelector:@selector(testPerform) withObject:nil afterDelay:0] 方法调用前后,通过控制台打印runloop对象,确实看到了调用方法后,runloop里多了一个timer源。

前后runloop对比:

Runloop与performSelector
有了runloop也有了触发方法 testPerform 执行的timer,为什么还依然没有执行。因为runloop没有跑起来。 所以创建完runloop后,还需要runloop跑起来。【通过给当前runloop添加观察者,查看runloop的状态,runloop没有跑起来】当我们调用 [runloop run]; 方法后,将runloop跑起来后, testPerform

才会执行。打印结果为1,2,3。

但,问题又来了,既然加入了runloop,并且跑起来了,为什么3还会打印出来,runloop不是相当于死循环吗?循环外的3为什么会打印出来?这个问题,通过加入的runloop的观察者的打印情况可以看出来,是因为,runloop在执行完 testPerform 后,就退出了。所以下边的3页打印出来了。

观察者打印:

Runloop与performSelector
可以看出,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就不会退出了,只是在不需要执行任务的时候进入休眠。

Runloop与performSelector
我在子线程中加入了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观察者的代码:

- (void)addObserver
{
    /*
     kCFRunLoopEntry = (1UL << 0),1
     kCFRunLoopBeforeTimers = (1UL << 1),2
     kCFRunLoopBeforeSources = (1UL << 2), 4
     kCFRunLoopBeforeWaiting = (1UL << 5), 32
     kCFRunLoopAfterWaiting = (1UL << 6), 64
     kCFRunLoopExit = (1UL << 7),128
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case 1:
            {
                NSLog(@"进入runloop");
            }
                break;
            case 2:
            {
                NSLog(@"timers");
            }
                break;
            case 4:
            {
                NSLog(@"sources");
            }
                break;
            case 32:
            {
                NSLog(@"即将进入休眠");
            }
                break;
            case 64:
            {
                NSLog(@"唤醒");
            }
                break;
            case 128:
            {
                NSLog(@"退出");
            }
                break;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);//将观察者添加到common模式下,这样当default模式和UITrackingRunLoopMode两种模式下都有回调。
    self.obsever  = observer;
    CFRelease(observer);
}

复制代码

本篇记录算是自己的理解,水平有限,如果有错误的地方,请批评指正,会尽快修改。

参考致谢:

iOS底层原理总结 - RunLoop

关于 performSelector 的一些小探讨

NSRunLoop的退出方式


以上所述就是小编给大家介绍的《Runloop与performSelector》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

响应式Web设计实践

响应式Web设计实践

[美] Tim Kadlec / 侯鸿儒 / 人民邮电出版社 / 2013-3-1 / 55.00元

随着各种各样的移动设备不断地涌现到使用者面前,Web设计的适应性已经成为设计师们所面临的最为艰巨的挑战。你设计出的网站不仅要在桌面计算机的大尺寸屏幕上可以为用户提供友好的UI和用户体验,同时在小尺寸屏幕上也应该可以提供一致的用户体验,并可以让用户能够在桌面大屏幕上和移动小屏幕上平滑切换,同时没有任何的不适应感觉。 本书作者是一位出色的开发者,在本书中,他将诸多技术和设计理念杂糅在一起,再辅以......一起来看看 《响应式Web设计实践》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试