iOS中如何正確釋放GCD定時器(dispatch_source_t)以及防止Crash?

Dispatch Source Timer 的使用以及注意事項

Dispatch Source Timer 是一種與 Dispatch Queue 結(jié)合使用的定時器。當需要在后臺 queue 中定期執(zhí)行任務(wù)的時候,使用 Dispatch Source Timer 要比使用 NSTimer 更加自然,也更加高效(無需在 main queue 和后臺 queue 之前切換)。

使用如下:

@property (nonatomic,strong) dispatch_source_t timer;

/** 創(chuàng)建定時器對象

* para1: DISPATCH_SOURCE_TYPE_TIMER 為定時器類型

* para2-3: 中間兩個參數(shù)對定時器無用

* para4: 最后為在什么調(diào)度隊列中使用

*/

_gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));

/** 設(shè)置定時器

* para2: 任務(wù)開始時間

* para3: 任務(wù)的間隔

* para4: 可接受的誤差時間,設(shè)置0即不允許出現(xiàn)誤差

* Tips: 單位均為納秒

*/

dispatch_source_set_timer(_gcdTimer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);

/** 設(shè)置定時器任務(wù)

* 可以通過block方式

* 也可以通過C函數(shù)方式

*/

dispatch_source_set_event_handler(_gcdTimer, ^{

? ? static int gcdIdx = 0;

? ? NSLog(@"GCD Method: %d", gcdIdx++);

? ? NSLog(@"%@", [NSThread currentThread]);

});

// 啟動任務(wù),GCD計時器創(chuàng)建后需要手動啟動

dispatch_resume(_gcdTimer);

如果需要暫停定時器調(diào)用:

dispatch_suspend(timer);

dispatch_suspend 和 dispatch_resume 應(yīng)該是成對出現(xiàn)的。兩者分別會減少和增加 dispatch 對象的掛起計數(shù),但是沒有 API 獲取當前是掛起還是執(zhí)行狀態(tài),所以需要自己記錄。


dispatch_suspend 暫停隊列并不意味著當前執(zhí)行的 block 暫停

當暫停派發(fā)隊列時需要注意,調(diào)用 dispatch_suspend 暫停一個隊列,并不意味著暫停當前正在執(zhí)行的 block,而是 block 可以執(zhí)行完,但是接下來的 block 會被暫停,直到 dispatch_resume 被調(diào)用。

dispatch_suspend 狀態(tài)下無法釋放

如果調(diào)用 dispatch_suspend 后 timer 是無法被釋放的。一般情況下會發(fā)生崩潰并報“EXC_BAD_INSTRUCTION”錯誤,看下?GCD 源碼dispatch source release 的時候判斷了當前是否是在暫停狀態(tài)。

所以,dispatch_suspend 狀態(tài)下直接釋放當前控制器或者釋放定時器,會導致定時器崩潰。

并且初始狀態(tài)(未調(diào)用dispatch_resume)、掛起狀態(tài),都不能直接調(diào)用dispatch_source_cancel(timer),調(diào)用就會導致app閃退。

建議一:盡量不使用dispatch_suspend,在dealloc方法中,在dispatch_resume狀態(tài)下直接使用dispatch_source_cancel來取消定時器。

建議二:使用懶加載創(chuàng)建定時器,并且記錄當timer 處于dispatch_suspend的狀態(tài)。這些時候,只要在 調(diào)用dealloc 時判斷下,已經(jīng)調(diào)用過 dispatch_suspend 則再調(diào)用下 dispatch_resume后再cancel,然后再釋放timer。


停止 Timer

停止 Dispatch Timer 有兩種方法,一種是使用?dispatch_suspend,另外一種是使用?dispatch_source_cancel。

dispatch_suspend?嚴格上只是把 Timer 暫時掛起,它和?dispatch_resume?是一個平衡調(diào)用,兩者分別會減少和增加 dispatch 對象的掛起計數(shù)。當這個計數(shù)大于 0 的時候,Timer 就會執(zhí)行。在掛起期間,產(chǎn)生的事件會積累起來,等到 resume 的時候會融合為一個事件發(fā)送。

dispatch_source_cancel?則是真正意義上的取消 Timer。被取消之后如果想再次執(zhí)行 Timer,只能重新創(chuàng)建新的 Timer。這個過程類似于對 NSTimer 執(zhí)行?invalidate。

關(guān)于取消 Timer,另外一個很重要的注意事項,dispatch_suspend?之后的 Timer,是不能被釋放的!下面的代碼會引起崩潰:

- (void)dealloc

{

? ? dispatch_suspend(_timer);

_timer =nil;// EXC_BAD_INSTRUCTION 崩潰

}

因此使用?dispatch_suspend?時,Timer 本身的實例需要一直保持。使用?dispatch_source_cancel?則沒有這個限制:


- (void)dealloc

{

? ? dispatch_source_cancel(_timer);

_timer =nil;// OK

}

總結(jié):

Dispatch Source使用最多的就是用來實現(xiàn)定時器,source創(chuàng)建后默認是暫停狀態(tài),需要手動調(diào)用dispatch_resume啟動定時器。

Dispatch Source定時器使用時也有一些需要注意的地方,不然很可能會引起crash:

1、循環(huán)引用:因為dispatch_source_set_event_handler回調(diào)是個block,在添加到source的鏈表上時會執(zhí)行copy并被source強引用,如果block里持有了self,self又持有了source的話,就會引起循環(huán)引用。正確的方法是使用weak+strong或者提前調(diào)用dispatch_source_cancel取消timer。

2、dispatch_resume和dispatch_suspend調(diào)用次數(shù)需要平衡,如果重復調(diào)用dispatch_resume則會崩潰,因為重復調(diào)用會讓dispatch_resume代碼里if分支不成立,從而執(zhí)行了DISPATCH_CLIENT_CRASH("Over-resume of an object")導致崩潰。

3、source在suspend狀態(tài)下,如果直接設(shè)置source = nil或者重新創(chuàng)建source都會造成crash。正確的方式是在resume狀態(tài)下調(diào)用dispatch_source_cancel(source)釋放當前的source。

4、盡量不使用dispatch_suspend,在dealloc方法中,在dispatch_resume狀態(tài)下直接使用dispatch_source_cancel來取消定時器。

5、使用懶加載創(chuàng)建定時器,并且記錄當timer 處于dispatch_suspend的狀態(tài)。這些時候,只要在 調(diào)用dealloc 時判斷下,已經(jīng)調(diào)用過 dispatch_suspend 則再調(diào)用下 dispatch_resume后再cancel,然后再釋放timer。

本文轉(zhuǎn)載自??https://blog.csdn.net/u013602835/article/details/8762349

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容