由於 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 : NSProxy
NS_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 開發交流群