• 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];
    

      

  • 相关阅读:
    插入排序
    JavaMail学习笔记
    汉诺塔问题
    使用Three.js绘制一个虚拟城市
    jquery flotcharts使用简介
    用CSS hack技术解决浏览器兼容性问题.
    IE条件注释详解.
    让IE6也认识!important
    代码重构(转)
    模版+数据分离渲染方式的设计与实现
  • 原文地址:https://www.cnblogs.com/shuigu/p/6351300.html
Copyright © 2020-2023  润新知