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的方法设计了invalidate
和fire
,在每一次调用invalidate的时候使用信号量加了一个锁,用来保证在多线程使用的时候的安全性。