NSTimer
可能你們都熟悉,他的api
也都很簡單,可是其使用過程並不容易,相信用過的同窗都踩過坑.一般咱們這麼用:ios
// 定義 @property (nonatomic, strong) NSTimer *timer; // 使用 self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(demo:) userInfo:nil repeats:YES]; 複製代碼
1.
timerWithTimeInterval
開頭的方法須要本身添加到指定的runloop
中去,而scheduledTimerWithTimeInterval
開頭的方法默認添加到當前的runloop
中去.git
2.
默認添加到runloop
中的NSTimer
會以NSDefaultRunLoopMode
的模式放入當前的runloop
,這就致使常常出現的列表滾動timer
中止的問題,由於列表滾動的時候runloop
的mode
切換了,須要咱們手動將timer
的runloop
切換爲commonMode
.github
3.
咱們常常會爲使用NSTimer
出現的內存泄漏而煩惱,即便有解決方案,總感受很彆扭.api
爲何會內存泄漏呢?咱們看一下api
的描述markdown
timer
會一直強引用target
,直到timer
調用invalide
方法,在[timer invalidate]
調用以前,timer
和vc
構成了循環引用
的關係,因此在咱們vc
在退出當前頁面的時候dealloc
方法並不調用,因此不論咱們的timer
是strong
仍是weak
都無濟於事,由於api
內部會強持有。app
網上有不少種方案去實現打破這種循環引用來解決內存泄漏問題,這裏咱們從自定義timer
提及,自定義timer說到底就是用dispatch_source
這套api
去實現.異步
Dispatch Source Timer
是一種與dispatch queue
結合使用的定時器,當須要在後臺queue
中按期執行任務的時候,使用dispatch source timer
要比使用NSTimer
更天然,更高效(由於無需再main queue
和 異步queue
之間切換)。ide
官方文檔提供建立timer
的示例是:oop
dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block) { dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); if (timer) { dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway); dispatch_source_set_event_handler(timer, block); dispatch_resume(timer); } return timer; } 複製代碼
注意點:ui
1.
此處的timer
是間隔定時器,每隔一段時間就會觸發而不須要像NSTimer
那樣設值repeats
.
2.
dispatch_source_set_timer
中的第二個參數能夠傳入dispatch_time
或者 dispatch_walltime
,dispatch_time
表示選擇是默認鐘錶來計時,這個會隨着系統休眠而休眠,而 dispatch_walltime
可讓定時間按照實際時間一直運行下去,因此針對間隔時間比較久的定時器應採用dispatch_walltime
,上述例子中的dispatch_walltime(NULL, 0)
等同於 dispatch_time(DISPATCH_WALLTIME_NOW, 0)
.
3.
dispatch_source_set_timer
中的第四個參數leeway 表明的是指望容忍時間,若是設值爲1
,意味着定時器時間達到前一秒或者後一秒才真正觸發定時器,計時指定leeway
的值爲0
,系統也沒法保證徹底精確的觸發時間,只是儘量的知足這個需求.
4.
dispatch_source_set_event_handler
的參數block
表明的是定時器間隔要執行的任務,它是綁定在執行的queue
上的,這個相比NSTimer
要方便太多,因爲NSTimer
須要Runloop
支持,NSTimer
則須要手動添加到指定線程的runloop
中去才能執行.
開啓timer的方法:
dispatch_resume
中止Dispatch Timer有兩種方法:
dispatch_suspend
:只是暫時把timer
掛起,須要和dispatch_resume
配對使用,在掛起期間,產生的事件會積累,等到resume
的時候會整合爲一個事件發送.dispatch_source_cancel
:至關於NSTimer
的invalidate
.結合上述的理解,咱們嘗試自定義timer
,一樣咱們模仿NSTimer
的接口,這樣使用起來沒有違和感也更方便:
@interface SSTimer : NSObject /// 同下面的方法,不過自動開始執行 + (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; /// 建立一個定時器並返回,可是並不會自動執行,須要手動調用resume方法 /// - parameter: start 定時器啓動時間 /// - parameter: ti 間隔多久開始執行selector /// - parameter: s 執行的任務 /// - parameter: ui 綁定信息 /// - parameter: rep 是否重複 - (instancetype)initWithTimeInterval:(NSTimeInterval)start interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep; /// 擴充block + (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(SSTimer *timer))block; /// 啓動 - (void)resume; /// 暫定 - (void)suspend; /// 關閉 - (void)invalidate; @property (readonly) BOOL repeats; @property (readonly) NSTimeInterval timeInterval; @property (readonly, getter=isValid) BOOL valid; @property (nullable, readonly, retain) id userInfo; @end 複製代碼
這裏咱們提供了啓動和暫停的功能,相比NSTimer
要好用不少,同時也擴充了block
的參數,雖然apple
也提供了block
的參數方法,可是須要在ios10
以上的系統上才能使用。
#import "SSTimer.h" #define lock(...) \ dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);\ __VA_ARGS__;\ dispatch_semaphore_signal(_semaphore); @implementation SSTimer { BOOL _valid; NSTimeInterval _timeInterval; BOOL _repeats; __weak id _target; SEL _selector; dispatch_source_t _timer; dispatch_semaphore_t _semaphore; id _userInfo; BOOL _running; } + (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo { SSTimer *timer = [[SSTimer alloc] initWithTimeInterval:0 interval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo]; [timer resume]; return timer; } + (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(SSTimer *timer))block { NSParameterAssert(block != nil); SSTimer *timer = [[SSTimer alloc] initWithTimeInterval:0 interval:interval target:self selector:@selector(ss_executeBlockFromTimer:) userInfo:[block copy] repeats:repeats]; [timer resume]; return timer; } - (instancetype)initWithTimeInterval:(NSTimeInterval)start interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep { self = [super init]; if (self) { _valid = YES; _timeInterval = ti; _repeats = rep; _target = t; _selector = s; _userInfo = ui; _semaphore = dispatch_semaphore_create(1); __weak typeof(self) weakSelf = self; _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), ti * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(_timer, ^{[weakSelf fire];}); } return self; } - (void)fire { if (!_valid) {return;} lock(id target = _target;) if (!target) { [self invalidate]; } else { // 執行selector #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [target performSelector:_selector withObject:self]; #pragma clang diagnostic pop if (!_repeats) { [self invalidate]; } } } - (void)resume { if (_running) return; dispatch_resume(_timer); _running = YES; } - (void)suspend { if (!_running) return; dispatch_suspend(_timer); _running = NO; } - (void)invalidate { dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); if (_valid) { dispatch_source_cancel(_timer); _timer = NULL; _target = nil; _userInfo = nil; _valid = NO; } dispatch_semaphore_signal(_semaphore); } - (id)userInfo { lock(id ui = _userInfo) return ui; } - (BOOL)repeats { lock(BOOL re = _repeats) return re; } - (NSTimeInterval)timeInterval { lock(NSTimeInterval ti = _timeInterval) return ti; } - (BOOL)isValid { lock(BOOL va = _valid) return va; } - (void)dealloc { [self invalidate]; } + (void)ss_executeBlockFromTimer:(SSTimer *)aTimer { void (^block)(SSTimer *) = [aTimer userInfo]; if (block) block(aTimer); } @end 複製代碼
這裏咱們使用了__weak id _target
,這樣咱們就內部就切斷了這層循環引用問題,作到自主釋放,外層使用方也沒必要關心,也不容易出現錯誤和內存泄漏. 具體的代碼在SSTimer下載,須要的能夠前去查看,目前內部也只是定義在main queue
上執行任務,後續會添加關於不一樣queue
上執行任務的timer
方法。還請不吝賜教和點贊支持,謝謝。