iOS--RunLoop和NSTimer

作者: 黑白灰的绿i | 来源:发表于2017-03-24 10:53 被阅读182次

    一. 什么是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。

    相关文章

      网友评论

      • 29d38d602d31:GCD的timer是自己管理的,不会被添加到runloop中
      • 幸运者_Lucky:子线程的 runloop 默认是关闭的, 只要给线程添加 Timer、Observer、Source, 并启动 runloop, runloop 就不会退出, 线程也不会销毁

      本文标题:iOS--RunLoop和NSTimer

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