iOS 不同场景下的定时方法

2,183 阅读4分钟
原文链接: www.jianshu.com

在之前的开发随笔小心NSTimer中的循环引用中介绍了NSTimer会因持有目标对象而引起内存泄漏(循环引用)以及解决办法.这篇随笔主要介绍iOS开发中其他几种实现定时任务的方式及其优缺点.

再谈NSTimer
要知道NSTimer工作方式,需要了解一下Runloop(这里只是给出描述,后续随笔会介绍).Runloop简单地说就是一个接收处理异步消息事件的循环,该循环中等待事件发生,然后将这个事件送到能处理它的地方。

或许你还不是很明白,没有关系,有个初步概念就不影响你理解NSTimer了.

NSTimer注册到Runloop中后,Runloop会周期性地触发注册的事件.但是这里的周期并不十分精确.Timer的属性Tolerance(容差)告诉系统可以允许的时间误差.一旦有大型的任务,错过这个周期需要执行的时间点±Tolerance,任务不会延后执行而是跳跃过去,下个周期继续.

NSObject的定时方法
这里不会介绍NSObject的所有方法,主要是介绍其应该注意的地方.在介绍之前补充一句:主线程会自动创建Runloop,子线程不会创建.

- performSelector:withObject:afterDelay:

如果该方法在主线程,可以正常执行,在子线程就会失效.这一系列方法有很多,如果读者感兴趣,可以阅读相关技术博客.这些方法的使用不是这篇随笔介绍的重点,因为这样博客有很多,再此重复意义不大.

CADisplayLink

A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
Your application creates a new display link, providing a target object and a selector to be called when the screen is updated.

上面摘自苹果官方文档,CADisplayLink是一个定时器,使用它可以以和
屏幕刷新频率相同的频率将内容绘到屏幕上.创建一个该类对象,设置好target和selector,在屏幕更新时就会调用该selector.

其使用方法和NSTimer类似,这里不再赘述.下面介绍一个使用场合,如果读者需要用到,可以自行查阅相关资料,查阅资料无法解决时,也可以在评论一起探讨.

CADisplayLink默认使用屏幕固有的刷新频率,精确度比较高,使用场景也较为单一.一般用在需要不断重绘界面时,比如UI动画底层实现.在实际应用中,没有NSTimer使用的多.

如果对CADisplayLink感兴趣,可以点击阅读一个使用CADisplayLink开源库的源码.


类似Secret文字渐变效果的开源库

GCD定时
Runloop是用GCD的dispatch_source_t 实现的 Timer,GCD定时器不依赖RunLoop和mode,比NSTimer更加准时,性能更好.

使用GCD实现定时功能也很简单.

#import "ViewController.h"

@interface ViewController () {
    dispatch_source_t _timer;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 获得一个queue
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // ①创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    /**函数原型
      dispatch_source_create(dispatch_source_type_t type,
      uintptr_t handle,
      unsigned long mask,
      dispatch_queue_t queue);
     *@param start
     * 开始时间
     *
     * @param interval
     *时间间隔
     *
     * @param leeway
     * 容差(精确度)
     */
    // ②设置定时开始时间 间隔 精确度
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
     // ③设置定时执行任务
    dispatch_source_set_event_handler(timer, ^{

        static NSInteger cnt = 1;
        NSLog(@"------------%ld", cnt++);
    });
    // ④激活定时器
    dispatch_resume(timer);
   // 持有定时器 避免ViewDidLoad方法结束timer变量消失引起定时器销毁
    _timer = timer;
}

@end

GCD的使用很简单,不用担心记不住dispatch_source_t前几个字母时Xcode自动会提示你了.你要做的只是创建一个queue而已,其他代码都不用你写.


dispatch_source_t提示

几种实现定时任务的比较

方法来源 使用
NSObject中的方法 适合延时执行任务,可以在子线程,也可回到主线程刷新UI.在子线程中延时时,必须自己创建Runloop
NSTimer 一般延时和周期性任务都可以使用,在子线程中使用时,必须自己创建Runloop.使用比较简单,存在循环引用的风险,解决办法参考小心NSTimer中的循环引用
CADisplayLink 适合重复重绘界面,其频率和屏幕刷新固有频率相同
GCD中的方法 可以代替NSTimer,使用比较简单.API是C语言格式的,一些读者可能不习惯

实现多任务定时器
有时候,会需要同时定时执行多个任务,灵活取消之前的任务,随时添加新任务,该怎么实现呢?如果感兴趣,请继续关注.笔者会在尽快实现该功能,到时候会及时更新.