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ù)需要平衡嘀粱,如果重復(fù)調(diào)用dispatch_resume則會崩潰,因為重復(fù)調(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