除了NSTimer还有两种定时器:CADisplayLink & Dispatch Source Timer

CADisplayLink
  • 是什么
    NSTimer相比,CADisplayLink是一个精确度很高的定时器,它会在屏幕每一次刷新结束以后调用它的target方法。iOS设备的屏幕刷新率是60HZ也就是每秒60次
    NSTimer的精确度就没这么高,而且NSTimer并不是一个严格可靠的Timer。正常情况下它是可以按照制定的周期去触发,但在当前线程有阻塞的时候就会延迟执行,超过一个周期会和下一个触发合并在一起执行。在UI相关的绘制相关的功能场景下一般使用CADisplayLink

  • 怎么用
    CADisplayLink在API设计上和NSTimer很相似,具体使用的时候参考NSTimer。同样的,也要注意循环引用的问题,具体如何解决参考我在NSTimer在使用中需要注意的点里面总结的方案。

    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(labelWalk)];
    self.displayLink.paused = YES;
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    -(void)labelWalk{}
    - (void)startAnimation{
        self.beginTime = CACurrentMediaTime();
        self.displayLink.paused = NO;
    }
    - (void)stopAnimation{
        self.displayLink.paused = YES;
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    
Dispatch Source Timer
  • 是什么
    Dispatch Source Timer是一种与Dispatch Queue结合使用的定时器,不依赖run loop系统触发更加高效。YYKit里面的YYTimer就是以这个为核心来实现的。

  • 怎么用
    Xcode提供了Dispatch Source Timer的代码块,其中leeway参数是允许误差,传0即可。定时器创建完成后,需要手动触发才能走起来,调用dispatch_resume这个方法即可。

      dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, <#dispatchQueue#>);
      dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, <#intervalInSeconds#> * NSEC_PER_SEC, <#leewayInSeconds#> * NSEC_PER_SEC);
      dispatch_source_set_event_handler(timer, ^{
          <#code to be executed when timer fires#>
      });
      dispatch_resume(timer);
    

下面是YYTimer的具体实现:

@implementation YYTimer {
    BOOL _valid;
    NSTimeInterval _timeInterval;
    BOOL _repeats;
    __weak id _target;
    SEL _selector;
    dispatch_source_t _source;
    dispatch_semaphore_t _lock;
}
- (instancetype)initWithFireTime:(NSTimeInterval)start
                            interval:(NSTimeInterval)interval
                              target:(id)target
                            selector:(SEL)selector
                             repeats:(BOOL)repeats {
        self = [super init];
        _repeats = repeats;
        _timeInterval = interval;
        _valid = YES;
        _target = target;
        _selector = selector;
        
        __weak typeof(self) _self = self;
        _lock = dispatch_semaphore_create(1);
        _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
        dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, (start * NSEC_PER_SEC)), (interval * NSEC_PER_SEC), 0);
        dispatch_source_set_event_handler(_source, ^{[_self fire];});
        dispatch_resume(_source);
        return self;
    }

作者为了解决了NSTimer容易造成循环引用的地方,有意地将_target设置成一个弱指针。

- (void)invalidate {
    dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
    if (_valid) {
        dispatch_source_cancel(_source);
        _source = NULL;
        _target = nil;
        _valid = NO;
    }
    dispatch_semaphore_signal(_lock);
}

- (void)fire {
    if (!_valid) return;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
    id target = _target;
    if (!target) {
        dispatch_semaphore_signal(_lock);
        [self invalidate];
    } else {
        dispatch_semaphore_signal(_lock);
        [target performSelector:_selector withObject:self];
        if (!_repeats) {
            [self invalidate];
        }
    }
#pragma clang diagnostic pop
}
}

仿照NSTimer的方法设计了invalidatefire,在每一次调用invalidate的时候使用信号量加了一个锁,用来保证在多线程使用的时候的安全性。

你可能感兴趣的:(除了NSTimer还有两种定时器:CADisplayLink & Dispatch Source Timer)