1. 程式人生 > >頁面實現多個定時器(計時器)時選用NSTimer還是GCD?(幹貨不濕)

頁面實現多個定時器(計時器)時選用NSTimer還是GCD?(幹貨不濕)

self. spa inf ima efault baidu 設定 common ref

定時器在我們每個人做的iOS項目裏面必不可少,如登錄頁面倒計時、支付期限倒計時等等,一般來說使用NSTimer創建定時器:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

But 使用NSTimer需要註意一下幾點:

1、必須保證有一個活躍的RunLoop。

系統框架提供了幾種創建NSTimer的方法,其中以scheduled開頭的方法會自動把timer加入當前RunLoop,到了設定時間就會觸發selector方法,而沒有scheduled開頭的方法則需要手動添加timer到一個RunLoop中才會有效。程序啟動時,會默認啟動主線程的RunLoop並在程序運行期內有效,所以把timer放入主線程時不需要啟動RunLoop,但現實開發中主線程更多的是處理UI事物,把耗時且耗能的操作放在子線程中,這就需要將子線程的RunLoop激活。

我們不難知道RunLoop在運行時一般有兩個:NSDefaultRunLoopMode、NSEventTrackingRunLoopMode,scheduled生成的timer會默認添加到NSDefaultRunLoopMode,當某些UI事件發生時,如頁面滑動RunLoop切換到NSEventTrackingRunLoopMode運行,我們會發現定時器失效,為了解決timer失效的問題,我們需要在scheduled一個定時器的時候,設置它的運行模式為:

[[NSRunLoop currentRunLoop] addTimer:self.progressTimer forMode:NSRunLoopCommonModes];

註意:NSRunLoopCommonModes並不是一種正在存在的運行狀態,這個模式等效於NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結合,相當於它標記了timer可以在這兩種模式下都有效。

2.NSTimer的創建與撤銷必須在同一個線程操作,不能跨越線程操作。

3.存在內存泄漏的風險(這個問題需要引起重視)

scheduledTimerWithTimeInterval方法將target設為A對象時,A對象會被這個timer所持有,也就是會被retain一次,timer又會被當前的runloop所持有。使用NSTimer時,timer會保持對target和userInfo參數的強引用。只有當調取了NSTimer的invalidate方法時,NSTimer才會釋放target和userInfo。生成timer的方法中如果repeats參數為NO,則定時器觸發後會自動調取invalidate方法。如果repeats參數為YES,則需要手動調取invalidate方法才能釋放timer對target和userIfo的強引用。

- (void)cancel{

[_timer invalidate];

_timer = nil;

}

這裏要特別註意的一點是,按照各種資料顯示,我們在銷毀或者釋放對象時,大部分都是在dealloc方法中,然後我們高高興興的在dealloc裏寫上

- (void)dealloc{

[self cancel];

}

以為這樣就可以釋放timer了,不幸的是,dealloc方法永遠不會被調用。因為timer的引用,對象A的引用計數永遠不會降到0,這時如果不調用cancel,對象X將永遠無法釋放,造成內存泄露。所以我建議在使用定時器的事件完成後立即將timer進行cancel,如果是比較長時間的定時器,可以在頁面消失事件中調用,如:

- (void)viewWillDisappear:(BOOL)animated{

[super viewWillDisappear:animated];

[self cancel];

}

看到這裏,你會不會發現使用NSTimer實現定時器這麽麻煩,又是RunLoop,又是線程的,一會兒還得考慮內存泄露,So , 如果在一個頁面需要同時顯示多個計時器的時候,NSTimer簡直就是災難了。那麽有沒有高逼格的辦法實現呢?答案就是GCD! 以下5點是使用dispatch_source_t創建timer的主要知識點:

1.獲取全局子線程隊列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

2.創建timer添加到隊列中

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

3.設置首次執行事件、執行間隔和精確度

dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);

4.處理事件block

dispatch_source_set_event_handler(timer, ^{

// doSomething()

});

5.激活timer / 取消timer

dispatch_resume(timer); / dispatch_source_cancel(timer);

寫到這裏,自然要問如果我只是想執行一次,不需要循環實現定時器那怎麽辦呢?那也沒問題,參考NSTimer,我們可以集成repeats選項,當repeats = No時,在激活timer並回調block事件後dispatch_source_cancel掉當前dispatch_source_t timer即可,如下所示:

技術分享

技術分享

上面的代碼就創建了一個timer,如果repeats = NO,在一個周期完成後,系統會自動cancel掉這個timer;如果repeats=YES,那麽timer會一個周期接一個周期的執行,直到你手動cancel掉這個timer,你可以在dealloc方法裏面做cancel,這樣timer恰好運行於整個對象的生命周期中。這裏不必要擔心NSTimer因dealloc始終無法調而產生的內存泄漏問題,你也可以通過queue參數控制這個timer所添加到的線程,也就是action最終執行的線程。傳入nil則會默認放到子線程中執行。UI相關的操作需要傳入dispatch_get_main_queue()以放到主線程中執行。

寫到這裏,基本上可以滿足開發要求,然而我們可以更加變態,假設這樣的場景,每次開始新一次的計時前,需要取消掉上一次的計時任務 或者 將上一次計時的任務,合並到新的一次計時中,最終一並執行!針對這兩種場景,也已經集成到上面的接口scheduleGCDTimerWithName中。具體代碼請看demo!

github地址:https://github.com/BeckWang0912/ZTGCDTimer

頁面實現多個定時器(計時器)時選用NSTimer還是GCD?(幹貨不濕)