iOS 三种常用定时器NSTimer、CADisplayLink、GCD详解

在iOS开发过程中,我们常常需要在某个时间后执行某个方法,或者是按照某个周期一直执行某个方法,这时我们就需要用到定时器,而在iOS中,常用的定时器有以下三种NSTimerCADisplayLinkGCD,下面对这几种方法的创建、执行、销毁及优缺点做个详解:

1. NSTimer

创建方式

//方式一
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2
                                                  target:self
                                                selector:@selector(timerTest)
                                                userInfo:nil
                                                 repeats:YES];
//方式二
    self.timer = [NSTimer timerWithTimeInterval:2
                                         target:self
                                       selector:@selector(timerTest)
                                       userInfo:nil
                                        repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
  • 使用 scheduled 开头创建的定时器,系统会自动的把它加入到 runLoopNSDefaultRunLoopMode 中,缺点是不能更改mode模式
  • 使用 timerWithTimeInterval 开头创建的定时器,需要手动的把他添加到runLoop中,同时设置它的mode运行模式。
定时执行(以下就是我们设置定时器需要做的事件)
- (void)timerTest {
    NSLog(@"==========NSTimer===============");
}

暂停、继续

暂停
self.timer.fireDate = [NSDate distantFuture];
继续
self.timer.fireDate = [NSDate distantPast];
  • 应用场景 比如,在页面消失的时候关闭定时器,然后等页面再次打开的时候,又开启定时器(主要是为了防止它在后台运行,占用CPU)

销毁

  [self.timer invalidate];
  self.timer = nil;

RunLoop 有五种运行模式,其中第1、5种为苹果对外提供的mode

1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
  • 常见问题
    在我们平时开发中可能遇到过,当我们使用NSTimer每一段时间执行一些事情时滑动UIScrollViewNSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复。那是因为Mode的切换造成的,当我们在主线程使用定时器,此时RunLoopModekCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时我们滑动ScrollView时,RunLoopMode会切换到UITrackingRunLoopMode,因此在主线程的定时器就不在管用了,调用的方法也就不再执行了,当我们停止滑动时,RunLoopMode切换回kCFRunLoopDefaultMode,所有NSTimer就又管用了。

  • 解决方案

  1. 将NSTimer添加到两种模式下
  2. 使用占位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的都可以运行,而kCFRunLoopDefaultModeUITrackingRunLoopMode就被打上commonModel标记。

NSTimer 缺点

计时不精确:不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoopRunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。

2.CADisplayLink

  • 他是苹果专门提供的一个类,主要的优势在于他的执行频率是根据设备屏幕的刷新频率来计算的,也即是时间间隔最准确的定时器。

创建方式

 self.displayLink = [CADisplayLink displayLinkWithTarget:self
                                                   selector:@selector(timerTest)];
 [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

定时执行

- (void)timerTest {
    NSLog(@"==========CADisplayLink===============");
}

暂停、继续

添加一个按钮用于控制暂停继续(或其他点击事件)
pasued属性是控制计时器暂停与恢复,设置为YES的时候会暂停事件的触发。

- (void) displayLinkStart {
    self.displayLink.paused = !self.displayLink.paused;
}

销毁

- (void)stopDisplayLink {
    [self.displayLink invalidate];
    self.displayLink = nil;
}

优缺点

  • 优点: 依托于设备屏幕刷新频率触发事件,所以其触发时间上是最准确的。也是最适合做UI不断刷新的事件,过渡相对流畅,无卡顿感。

  • 缺点:

  1. 由于依托于屏幕刷新频率,若果CPU不堪重负而影响了屏幕刷新,那么我们的触发事件也会受到相应影响。
  2. selector触发的时间间隔只能是duration的整倍数
  3. selector事件如果大于其触发间隔就会造成掉帧现象。

相关属性

 @property(readonly, nonatomic) CFTimeInterval timestamp;    //获取上一次selector被执行的时间戳
 @property(readonly, nonatomic) CFTimeInterval duration; //获取当前设备的屏幕刷新时间间隔
 @property(getter=isPaused, nonatomic) BOOL paused;  //控制计时器暂停与恢复的属性
    
    //事件触发间隔。是指两次selector触发之间间隔几次屏幕刷新,默认值为1,也就是说屏幕每刷新一次,执行一次selector,这个也可以间接用来控制动画速度
 @property(nonatomic) NSInteger frameInterval
    CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond");
 @property(nonatomic) NSInteger preferredFramesPerSecond;

3. GCD-dispatch_source_t

创建 添加 执行方法

- (void)addDisPatch_source {
    //1.创建
    self.disTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    //2.设置时间
    dispatch_source_set_timer(self.disTimer, dispatch_walltime(NULL,0 * NSEC_PER_SEC), 1 * NSEC_PER_SEC, 0);
    //3.执行
    dispatch_source_set_event_handler(self.disTimer, ^{
        NSLog(@"=========dispatch_source_t=========");
    });
}

参数:

dispatch_source_create()相关参数

参数 意义
type dispatch源可处理的事件
handle 可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID
mask 可以理解为描述,提供更详细的描述,让它知道具体要监听什么
queue 自定义源需要的一个队列,用来处理所有的响应句柄(block)

dispatch_source_set_timer()相关参数

参数 意义
source dispatch_source_t
start 事件首次触发的延迟时间
interval 时间间隔
leeway 误差范围

开始 暂停

//开始
dispatch_resume(self.disTimer);
//暂停
dispatch_suspend(self.disTimer);

销毁

dispatch_source_cancel(self.disTimer);

注意:dispatch_source_t 一定要被设置为成员变量,否则将会立即被释放。

优缺点

  • 优点:不受当前runloopMode的影响。
  • 缺点:虽然不受runloopMode的影响,但是其计时效应仍不是百分之百准确的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容