封裝一個GCD定時器,徹底解決定時器迴圈引用、釋放時機問題
相信大家在開發中都會使用到定時器, 但又常常對定時器的迴圈引用問題, NSTimer 釋放時機的選擇上,勞神費力! 讀了本文,這些再也不是問題!
關於 NSTimer 建立定時器的方法,我就不多做描述了,網上很多例子,但也總覺得很麻煩。本文主要講使用GCD的方法。
今天在重構程式碼的時候,發現專案中好幾個地方都用到了定時器,就想著封裝一個定時器的方法,以後用著方便,也可以豐富自己的工具類庫;寫的時候,發現 GCD定時器方法並不被執行,細看之下感覺跟以前寫的也沒什麼區別啊,但為什麼就沒有執行呢?
void dispatchTimer(double timeInterval, void (^handler)())
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(timer,dispatch_time(DISPATCH_TIME_NOW, 0),1.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
handler();
});
});
dispatch_resume(timer);
}
細想一下,原來是因為 timer 是個區域性變數,當函式執行完之後,會被自動釋放,也就不會再執行handler的方法了;忽略了以前在控制器中實現的時候 都會用一個屬性來記錄下 timer;
如何保證 在執行 handler 時 timer 不被釋放呢? 我需要對 timer 有一個強引用, 如果按原來的思路在用到定時器的類裡用屬性記錄下timer,把timer作為引數傳入,我就需要每次呼叫這個函式的時候先建立好一個 定時器,那麼封裝的這個方法也就沒有什麼意義了。
後來想到一個方法: 給回撥 handler 裡新增一個 dispatch_source_t timer 引數,在 dispatch_source_set_event_handler 的block裡回撥 handler時將區域性變數 timer 回傳; 這個時候dispatch_source_set_event_handler的 block 相當於對 timer 進行了一次強引用,這樣一來 timer 也就不會被過早釋放了;
如有對 block 記憶體管理不慎明白的可以看一下這篇文章
void dispatchTimer(double timeInterval,void (^handler)(dispatch_source_t timer))
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer =dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), (uint64_t)(timeInterval *NSEC_PER_SEC), 0);
// 設定回撥
dispatch_source_set_event_handler(timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
//編譯時block會對timer物件強引用,使timer不會被過早釋放
if (handler) handler(timer);
});
});
// 啟動定時器
dispatch_resume(timer);
}
在使用的時候才發現,handler 裡新增的那個 dispatch_source_t timer 引數,原來也是必須要有的,我需要 timer物件 來結束定時器或銷燬定時器。o(╯□╰)o 啊,原以為解決了一個問題,沒想到這是必須的一個引數。o(╯□╰)o o(╯□╰)o o(╯□╰)o
後來考慮到,當使用定時器的物件銷燬的時候,可能並沒有手動對定時器進行釋放,於是又完善了一下, 添加了一個 target 引數,當 target 銷燬時,就將 定時器銷燬。哈哈,終於功德圓滿了。
先看看使用效果吧:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
__block int timeCount = 60;
dispatchTimer(self, 1.0, ^(dispatch_source_t timer) {
if (timeCount < 0) {
dispatch_source_cancel(timer);
} else {
NSLog(@"%d", timeCount);
timeCount -= 5;
}
});
}
注意:
呼叫 dispatch_source_cancel(timer); 就會將 timer 物件銷燬,唯一要考慮的就是 timer 的結束時機;即使你不主動釋放timer, 也不會造成迴圈引用哦 O(∩_∩)O~
上原始碼:
/**
開啟一個定時器
@param target 定時器持有者
@param timeInterval 執行間隔時間
@param handler 重複執行事件
*/
void dispatchTimer(id target, double timeInterval,void (^handler)(dispatch_source_t timer))
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer =dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), (uint64_t)(timeInterval *NSEC_PER_SEC), 0);
// 設定回撥
__weak __typeof(target) weaktarget = target;
dispatch_source_set_event_handler(timer, ^{
if (!weaktarget) {
dispatch_source_cancel(timer);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
if (handler) handler(timer);
});
}
});
// 啟動定時器
dispatch_resume(timer);
}
將此函式copy到你的工具類中就可以放心使用了。
媽媽再也不用擔心 我的定時器的迴圈引用問題了! 哈哈,有沒有很 激動!
如有問題,歡迎評論交流!