iOS 定时器开发详情

目录

  • 概述

  • NSTimer

  • performSelector

  • GCD timer

  • CADisplayLink

一、概述

  在平时的开发任务中,定时器是我们常用的技术。这一节我们来学习iOS怎么使用定时器。

  在iOS中用一个timer对象来表示一个定时器,这个timer对象必须关联到一个runloop对象才能够正常运行。也就是说,runloop对象是timer对象的拥有者,当定时器的时间到期时由runloop对象给timer发通知,所以runloop对象是持有timer对象的强引用的。如果是一次性的定时器话,当定时器到期时,runloop放弃持有timer对象的引用。但如果是循环timer的话 runloop会一直持有timer的引用直到timer调用invalidate。

  timer对象关联到runloop对象时需要指定一个runloop mode,默认为default。当定时器到期时,并且runloop运行的mode与timer所关联的mode相同情况下,runloop才会给timer发通知。

  假设timer关联到default mode,当runloop运行在 tracking modes时(滑动的时候),即使timer到期了也是不会被触发的。

  正是因为timer需要runloop才起作用所以timer是有误差的:当一次runloop循环时检查timer是否满足触发条件,如果不满足则等待下次循环再检查。timer的误差大概是50~100ms,可满足一般对精度要求不高的需求。  

  在iOS开发中,我们可以四种技术来开发定时器:

    1. NSTimer                  -   timer对象的cocoa类
    2. performSelector       -       NSObject的函数
    3. GCD timer               -       dispatch 内的
    4. CADisplayLink      -       与屏幕刷新频率一致的timer  

  一般来说,如果只是延迟执行的定时器,我们多会用display_after、NSObject的performSelector函数。

  一般的循环定时器可以用NSTimer跟 dispatch_source的timer。

  CADisplayLink则多用于动画或者视频开发当中

二、NSTimer

  创建一个timer的步骤是:

  1.创建一个NSTImer

  2.把timer添加到runloop

self.unscheduledTimer = [NSTimer timerWithTimeInterval:3
                            target:self
                          selector:@selector(targetMethod:)
                          userInfo:nil
                           repeats:NO];
    
    // 添加到runloop
    // NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    // [runLoop addTimer:self.unscheduledTimer forMode:NSRunLoopCommonModes];    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addTimer:self.unscheduledTimer forMode:NSRunLoopCommonModes];
    });

  上面代码中,创建一个3s后执行的timer,但是2s后才把这个timer添加到runloop中,3s后就会触发timer(也就是添加到runloop后1s)。

  假设我们4s钟后才把timer添加到runloop中,这时的timer会立马被触发。

  整过程是这样的,当一个timer被添加到runloop时,runloop会检查这个timer是否到期如果到期了就会立刻触发这个timer,如果没有过期就等待下次runloop时再检查。

  我们也可以用下面函数生成一个定时器:

[NSTimer scheduledTimerWithTimeInterval:5.0
                                     target:self
                                   selector:@selector(targetMethod:)
                                   userInfo:nil
                                    repeats:NO];

  scheduledTimerWithTimeInterval函数做两件事:

  1.生成一个timer并用传入的参数配置它

  2.把生成的timer以default mode添加到当前的runloop的。

 

三、performSelector

  performSelector则比较简单了,可以用于一般的延迟任务:

[self performSelector:@selector(targetMethod:) withObject:nil afterDelay:3];

四、GCD timer

  GCD的dispath_after函数很好用,可以用于一般的延迟任务:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 三秒钟后执行
    });  

 还有可以利用dispatch_source来创建timer

__block int count = 0;
    /*
     * dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)
     * 第一个参数指明这是一个timer,当然还可以指定其他类型
     * 第二个参数是一个系统资源的句柄,比如文件句柄
     * 第三个参数为flag
     * 第四个参数是handle block被提交到的queue
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    if (timer) {
        // 设置 timer
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1);
        // 设置 event handler
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"timer fire");
            if (count++ == 4) {
                // 取消dispatch source
                dispatch_source_cancel(timer);
            }
        });
        // 设置 event handler
        dispatch_source_set_cancel_handler(timer, ^{
            NSLog(@"time cancel.");
        });
        // 启动 dispatch source
        // 因为create后还需要配置一些行为,所以需要手动resume dispatch source
        dispatch_resume(timer);
        // 注意:
        // 这里需要保存一下timer到类变量,不然timer是局部变量运行到函数尾部时,这个定时器也就没了
        self.timer = timer;
    }

  需要说明的是,通过dispatch_source_create生成并返回的变量,不能是局部变量。因为如果是局部变量的话,在生命周期结束时这个source就被释放了。这一点与NSTimer不一样,因为当NSTimer被添加到runloop时,runloop就会持有NSTimer的强引用。

  所以如果我们需要一个定时器,并想让这个定时器生命周期跟我们的业务类的生命周期一致时,可以用dispatch_source的方式创建定时器,然后把这个定时器保存为我们的业务类的类成员变量。

五、CADispalyLink

  CADisplayLink是一个特殊的timer对象,特殊的在于这个timer的触发的频率跟屏幕刷新的频率是一致的。也就是说,每当屏幕刷新一次就会调用一次timer的回调函数,当然我们可以使设置frameInterval属性来指明刷新多少帧后才触发一次timer,frameInterval默认为1.

self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(targetMethod:)];
// [self.displayLink setFrameInterval:24] // 24帧回调一次
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

  

posted @ 2017-03-01 14:17  水谷  阅读(276)  评论(0编辑  收藏  举报