由于 NSTimer 要加到 runloop 中才能工作,这样的话 runloop 在跑圈的时候,如果遇到了当前线程任务比较繁忙,那么它处理 NSTimer 的时机就会滞后,导致 NSTimer 不够准时.因为我们可以用 GCD 的 dispatch_soure_t 去实现一个自己的定时器,而且还比较准时不受 Runloop 影响
YVTimer API 设计 尽可能的仿照 NSTimer
@interface YVTimer : NSObject/** 定时器类方法创建 立即开启 */+ (instancetype)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector repeats:(BOOL)yesOrNo;/** 定时器类方法创建 指定开启时间 */+ (instancetype)timerWithFireTime:(NSTimeInterval)start interval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector repeats:(BOOL)yesOrNo;- (instancetype)init UNAVAILABLE_ATTRIBUTE;+ (instancetype)new UNAVAILABLE_ATTRIBUTE;- (void) fire;- (void) invalidate;@property (readonly) BOOL repeats;@property (readonly) NSTimeInterval timeInterval;@property (readonly, getter=isValid) BOOL valid;@end复制代码
实现代码也非常简单 就是对 GCD dispatch_source_t 的封装
#import "YVTimer.h"#define LOCK [_lock lock]#define UNLOCK [_lock unlock]@interface YVTimer (){ id _target; NSTimeInterval _timeInterval; BOOL _repeats; dispatch_source_t _timer; SEL _selector; BOOL _valid; NSLock *_lock;}@end@implementation YVTimer+ (instancetype)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector repeats:(BOOL)yesOrNo { return [YVTimer timerWithFireTime:0.0f interval:ti target:aTarget selector:aSelector repeats:yesOrNo];}+ (instancetype)timerWithFireTime:(NSTimeInterval)start interval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector repeats:(BOOL)yesOrNo { return [[YVTimer alloc] initWithFireTime:start interval:ti target:aTarget selector:aSelector repeats:yesOrNo];}- (instancetype)initWithFireTime:(NSTimeInterval)start interval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector repeats:(BOOL)repeats { if (self = [super init]) { _target = target; _selector = selector; _repeats = repeats; _timeInterval = interval; _valid = YES; _lock = [[NSLock alloc] init]; __weak typeof(self)weakSelf = self; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,dispatch_get_main_queue()); dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, (start * NSEC_PER_SEC)), (interval * NSEC_PER_SEC), 0); dispatch_source_set_event_handler(timer, ^{ [weakSelf fire]; }); dispatch_resume(timer); _timer = timer; } return self;}- (void) fire { if (!_valid) return;#pragma clang diagnostic push#pragma clang diagnostic ignored "-Warc-performSelector-leaks" LOCK; id target = _target; if (!_target) { [self invalidate]; } else { [target performSelector:_selector withObject:self]; if (!_repeats) { [self invalidate]; } } UNLOCK;#pragma clang diagnostic pop}- (void)invalidate { LOCK; if (_valid) { dispatch_source_cancel(_timer); _timer = NULL; _target = nil; _valid = NO; } UNLOCK;}- (NSTimeInterval)timeInterval { LOCK; NSTimeInterval t = _timeInterval; UNLOCK; return t;}- (BOOL)repeats { LOCK; BOOL r = _repeats; UNLOCK; return r;}- (BOOL)isValid { LOCK; BOOL valid = _valid; UNLOCK; return valid;}- (void)dealloc{ [self invalidate]; NSLog(@"timer dealloc");}@end复制代码
如何使用 建议设置 timer target 时候 不要直接使用当前类 self ,可以通过代理方式 用其他类的对象 去替代 self, 这个代理对象 内部可以弱引用 self ,并通过消息转发 去相应 self要执行的方法
@interface YVProxy : NSProxyNS_ASSUME_NONNULL_BEGIN+ (instancetype) proxyWithTarget:(id)target;- (instancetype)initWithTarget:(id)target;@property (nonatomic, weak, readonly) id target;NS_ASSUME_NONNULL_END@end#import "YVProxy.h"@interface YVProxy ()@end@implementation YVProxy+ (instancetype)proxyWithTarget:(id)target { return [[YVProxy alloc] initWithTarget:target];}- (instancetype)initWithTarget:(id)target { _target = target; return self;}///消息转发 返回方法签名- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel];}/** 消息转发 将方法交给其他对象去调用 */- (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target];}- (void)dealloc{ NSLog(@"YVProxy dealloc");}@end复制代码
好了,我是大兵布莱恩特,欢迎加入博主技术交流群,iOS 开发交流群