iOS之OC深入理解程序员iOS Developer

iOS--RunLoop和NSTimer

2017-03-24  本文已影响182人  黑白灰的绿i

一. 什么是Runloop?

runloop就是运行循环,每一个应用程序想要保持活性,都会需要这样一个死循环,并不是iOS特有的,runloop可以保证程序不退出。runloop的每一次循环都需要负责时间的监听,例如定时器,触摸时间,网络事件等。当没有事件发生时,runloop会自动使程序休眠。

二. 理解Runloop。

(1) 观望runloop

以计时器为例子,我们这样创建一个计时器,运行,会打印出“计时器”

 NSTimer * timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];

-(void)updataTimer
{
    NSLog(@"计时器");
}

但是换一种方式创建计时器,那么将不会打印。

NSTimer * timer =[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];

因为这个计时器识没有被添加在当前的runloop中,需要我们手动添加。

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

timer就是我们刚刚创建的计时器,那么mode是什么呢?

我们在程序中创建一个表格,运行程序,当我们拖动表格的时候,可以明显的看到,计时器不打印了,就是因为这个mode 模式,接下来介绍这几种模式。

NSDefaultRunLoopMode --默认模式   建议将时钟事件和网络事件添加到这种模式

UITrackingRunLoopMode --这种模式是专门用来处UI事件的

还有两种模式,一个是处理系统事件的模式,一个是初始化程序的模式,这两个我们开发者是无法使用的。

NSRunLoopCommonModes --这个不是一种确切的模式,只是一个占位

当我们把上面的mode设置为UITrackingRunLoopMode的时候,我们拖动表格计时器才会运行,设置为NSRunLoopCommonModes的时候,计时器在哪种模式下都可以运行。而上面自动添加到runloop中的定时器初始化方法,就是添加在了NSDefaultRunLoopMode模式下。

runloop每循环一次,会在某一个模式下进行切换,去执行这一种模式下的事件。runloop会优先处理UI模式。
(2)触碰runloop(runloop与线程)

通过上面的讲述,我们知道,当创建一个计时器时,放在NSRunLoopCommonModes中是最好的,但是当在这里面做耗时操作界面会不会卡顿呢?如果卡顿该怎么办呢?
会卡顿的,不信你试试!!
我们直接来说该怎么办吧。😢😢
其实马上就可以想到,使用GCD创建子线程就可以了啊,我们来试一下

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSTimer * timer =[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    });

-(void)updataTimer
{
    NSLog(@"计时器");
}

运行一下可以看到,nstimer的方法不打印了。为什么呢?
以为我们的nstimer被创建在子线程,而在这个线程上面没有runloop循环,所以这个子线程在执行完毕之后被回收了,无法监听timer。
那么我们就需要让这个runloop跑起来。

[[NSRunLoop currentRunLoop] run];

运行,完美解决。这时候即使把mode换成NSDefaultRunLoopMode,拖动表格也不会打扰timer,因为是主线程在处理UI。继续来看,在这句代码下面加一个nslog,是不会打印的,因为这句代码是死循环。而且这种让runloop运行的方法是不被建议的,因为一旦开启,没有办法让其停止。
也有一种low逼的解决办法。既然这是一个死循环,那么我们可以创建一个自己的死循环去解决这个不能停止的问题。

 _finish=NO;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSTimer * timer =[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
        while (!_finish) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
        }
        
    });

-(void)updataTimer
{
    static int num = 0;
    NSLog(@"计时器");
    if (num>9) {
        self.finish=YES;
    }
}
(3)runloop番外

runloop不管在哪种模式下,都只会监听三种事件。

Source :

CFRunloopSourceRef 事件源(输出源)。
包含 source0 : 非系统事件 source1 : 系统事件
什么是系统事件和非系统事件呢?
简单的写一段代码。

[self performSelector:@selector(eat)];

-(void)eat
{
    NSLog(@"吃");
}

在调用处打断点,运行我们可以在调用栈中看到。

屏幕快照 2017-03-23 下午5.04.49.png
Observer :

runloop的观察者

timer :

计时器

(4)计时器番外(GCD定时器)

我们创建一个GCD的计时器并且让其运行起来。

@property(nonatomic,strong)dispatch_source_t timer;

 //   创建一个队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //   创建一个计时器
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //  启动计时器
    dispatch_resume(self.timer);

到这里还需要在启动前设置计时器的各种参数,需要用到这个方法dispatch_source_set_timer(<#dispatch_source_t _Nonnull source#>, <#dispatch_time_t start#>, <#uint64_t interval#>, <#uint64_t leeway#>)
第一个参数:我们要进行设置的对象->self.timer
第二个参数:什么时间开始->DISPATCH_TIME_NOW(立刻开始)
第三个参数:多长时间执行一次,GCD的时间是十分精确的,单位是纳秒,如果我们要设置为一秒->dispatch_time_t interval = 1.0 * NSEC_PER_SEC;
最后一个参数是0。
接下来好要为这个计时器设置一个回调函数。

dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"--------%@",[NSThread currentThread]);
    });

运行查看打印日志


24F87609-CC8E-41A1-804F-C7CE4CDA6C69.png

GCD中已经为我们封装好了runloop,所以不必考虑mode。

上一篇下一篇

猜你喜欢

热点阅读