1. 概述
說起計時器,很多開發人員第一時間就會想起Timer,但是隨著使用的深入,慢慢就發現Timer其實不是很好用,比如說TableView滑動時候不執行,Timer循環引用。
2. DispatchSourceTimer
DispatchSourceTimer,也就是大家通常叫的GCD Timer,是依賴于GCD的一種Timer,Runloop的底層代碼中也用到這種Timer,可見GCD Timer并不依賴與Runloop。
先看一下蘋果的定義:
A dispatch source that submits the event handler block based on a timer.
2.1 GCD Timer 創建
使用下面的方法即可創建一個DispatchSourceTimer對象。
class func makeTimerSource(flags: DispatchSource.TimerFlags = [], queue: DispatchQueue? = nil) -> DispatchSourceTimer
// 默認在主隊列中調度使用
let timer = DispatchSource.makeTimerSource()
// 指定在主隊列中調度使用
let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
// 指定在全局隊列中調度使用
let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global())
// 指定在自定義隊列中調度使用
let customQueue = DispatchQueue(label: "customQueue")
let timer = DispatchSource.makeTimerSource(flags: [], queue: customQueue)
2.2 GCD Timer 配置
配置Timer參數,需要使用DispatchSourceTimer協議的方法。可以安排一次或多次觸發的Timer。Timer每次觸發的時候,都會調用已部署的任務。
// 從現在開始,每秒執行一次。
timer?.schedule(deadline: DispatchTime.now(), repeating: .seconds(1), leeway: .nanoseconds(1))
// 5秒之后執行任務,不重復。
timer?.schedule(deadline: DispatchTime.now() + 5, repeating: .never, leeway: .nanoseconds(1))
2.3 GCD Timer 部署任務
當Timer配置完參數后,使用DispatchSourceProtocol協議的方法來部署要執行的任務。
setEventHandler和setRegistrationHandler的區別:
- setEventHandler:給Timer設置要執行的任務,包括一次性任務和定時重復的任務。回調方法在子線程中執行。
- setRegistrationHandler:這個方法設置的任務只會執行一次,也就是在Timer就緒后開始運行的時候執行,類似于Timer開始的一個通知回調。回調方法在子線程中執行。
例如下面的代碼:
var timer: DispatchSourceTimer?
func initTimer() {
// 默認在主隊列中調度使用
timer = DispatchSource.makeTimerSource()
// 從現在開始,每秒執行一次。
timer?.schedule(deadline: DispatchTime.now(), repeating: .seconds(1), leeway: .nanoseconds(1))
// 5秒之后執行任務,不重復。
// timer?.schedule(deadline: DispatchTime.now() + 5, repeating: .never, leeway: .nanoseconds(1))
timer?.setEventHandler {
DispatchQueue.main.async {
print("執行任務")
}
}
timer?.setRegistrationHandler(handler: {
DispatchQueue.main.async {
print("Timer開始工作了")
}
})
timer?.activate()
}
執行結果如下:
2020-11-28 02:20:00 +0000 Timer開始工作了
2020-11-28 02:20:00 +0000 執行任務
2020-11-28 02:20:01 +0000 執行任務
2020-11-28 02:20:02 +0000 執行任務
2.4 GCD Timer控制方法
下面看一下Timer的一下控制方法及狀態:
activate() : 當創建完一個Timer之后,其處于未激活的狀態,所以要執行Timer,需要調用該方法。
suspend() : 當Timer開始運行后,調用該方法便會將Timer掛起,即暫停。
resume() : 當Timer被掛起后,調用該方法便會將Timer繼續運行。
cancel() : 調用該方法后,Timer將會被取消,被取消的Timer如果想再執行任務,則需要重新創建。
上面的這些方法如果使用不當,很容易造成APP崩潰,下面來看一下具體注意事項及建議:當Timer創建完后,建議調用activate()方法開始運行。如果直接調用resume()也可以開始運行。
suspend()的時候,并不會停止當前正在執行的event事件,而是會停止下一次event事件。
當Timer處于suspend的狀態時,如果銷毀Timer或其所屬的控制器,會導致APP奔潰。
suspend()和resume()需要成對出現,掛起一次,恢復一次,如果Timer開始運行后,在沒有suspend的時候,直接調用resume(),會導致APP崩潰。
使用cancel()的時候,如果Timer處于suspend狀態,APP崩潰。
另外需要注意block的循環引用問題。
2.5 雙重循環 DispatchSourceTimer
比如:我們需要一定時間情況下(數組長度*4),每隔一段時間打印對應下標(每個元素隔4秒打印),無限打印
在下面例子的雙重循環中使用 DispatchSourceTimer 你會發現print只打印了 dom some = 0
這個不是我們想要的效果
var exaltedTimer: DispatchSourceTimer?
func demo {
let arr = [1,2,3,4]
self.exaltedTimer = DispatchSource.makeTimerSource()
self.exaltedTimer?.schedule(deadline: .now(), repeating: TimeInterval(arr.count*4))
self.exaltedTimer?.setEventHandler(handler: {
for (index, item) in arr.enumerated() {
let timer2 = DispatchSource.makeTimerSource()
timer2.schedule(deadline: .now()+4.0*CGFloat(index), repeating: .infinity)
timer2.setEventHandler {
DispatchQueue.main.async {
print("do some = \(index)")
}
}
timer2.activate()
}
})
self.exaltedTimer?.activate()
}
這個是因為timer2使用之后就被釋放了,所以要cancel和resume配合使用,這樣就實現了我們想要的效果了。
var exaltedTimer: DispatchSourceTimer?
var exaltedTimerArray: [DispatchSourceTimer] = []
func demo {
self.exaltedTimer?.cancel()
for subTimer in self.exaltedTimerArray {
subTimer.cancel()
}
let arr = [1,2,3,4]
self.exaltedTimer = DispatchSource.makeTimerSource()
self.exaltedTimer?.schedule(deadline: .now(), repeating: TimeInterval(arr.count*4))
self.exaltedTimer?.setEventHandler(handler: {
for (index, item) in arr.enumerated() {
let timer2 = DispatchSource.makeTimerSource()
timer2.schedule(deadline: .now()+4.0*CGFloat(index), repeating: .infinity)
timer2.setEventHandler {
DispatchQueue.main.async {
print("do some = \(index)")
}
}
self.exaltedTimerArray.append(timer2)
timer2.resume()
}
})
self.exaltedTimer?.resume()
3 OC GCD定時器 封裝
GCDTimer.h
@interface GCDTimer : NSObject
/**
* 對象創建定時器 主線程創建
* interval 定時時間
* repeat 是否輪詢
* block 返回
*/
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat block:(dispatch_block_t)block;
/**
* 對象創建定時器 主線程創建
* interval 定時時間
* repeat 是否輪詢
* queue 線程
* block 返回
*/
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat queue:(dispatch_queue_t)queue block:(dispatch_block_t)block;
/**
* 類創建定時器 主線程創建
* interval 定時時間
* repeat 是否輪詢
* block 返回
*/
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat queue:(dispatch_queue_t)queue block:(dispatch_block_t)block;
/**
* 類創建定時器 主線程創建
* interval 定時時間
* repeat 是否輪詢
* queue 線程
* block 返回
*/
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat block:(dispatch_block_t)block;
/**
* 設置定時器時間
* interval 定時時間
*/
- (void)setTimeInterval:(NSTimeInterval)interval;
/**
* 停止定時器
*/
- (void)stop;
/**
* 重啟定時器
*/
- (void)restart;
/**
* 釋放定時器
*/
- (void)invalidate;
@end
GCDTimer.m
@interface GCDTimer() {
dispatch_source_t _timer;
BOOL _isFire;
}
@end
@implementation GCDTimer
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat block:(dispatch_block_t)block {
return [self initWithTimeInterval:interval repeat:repeat queue:dispatch_get_main_queue() block:block];
}
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat queue:(dispatch_queue_t)queue block:(dispatch_block_t)block {
NSAssert(queue != NULL, @"queue can't be NULL while create GCDTimer");
if (self = [super init]) {
// 創建線程
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
// 執行任務
dispatch_source_set_event_handler(_timer, ^{
if (block) {
block();
}
if (!repeat) {
self->_isFire = NO;
dispatch_source_cancel(self->_timer);
}
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_resume(self->_timer);
self->_isFire = YES;
});
}
return self;
}
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat queue:(dispatch_queue_t)queue block:(dispatch_block_t)block {
return [[GCDTimer alloc] initWithTimeInterval:interval repeat:repeat queue:queue block:block];
}
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat block:(dispatch_block_t)block {
return [self scheduledTimerWithTimeInterval:interval repeat:repeat queue:dispatch_get_main_queue() block:block];
}
- (void)setTimeInterval:(NSTimeInterval)interval {
if (_isFire) {
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
}
}
- (void)stop {
if (_isFire) {
_isFire = NO;
// 掛起定時器隊列
dispatch_suspend(_timer);
}
}
- (void)restart {
if (!_isFire) {
_isFire = YES;
// 恢復定時器隊列
dispatch_resume(_timer);
}
}
- (void)invalidate {
_isFire = NO;
// 取消定時器隊列
dispatch_source_cancel(_timer);
}
- (void)dealloc {
_isFire = NO;
// 取消定時器隊列
dispatch_source_cancel(_timer);
}
@end
4 Swift GCD定時器 封裝
import Foundation
class CGDTimer: NSObject {
static let `default` = CGDTimer()
private var timer: DispatchSourceTimer?
deinit {
timer?.cancel()
}
// 暫停定時器
func stop() {
timer?.suspend()
}
// 重啟定時器
func restart() {
timer?.resume()
}
// 清除定時器
func invalidate() {
timer?.cancel()
}
// 主線程設置CGD定時器
// interval 時間
// isRepeat 是否循環
// block 執行返回
func setTimeInterval(interval: Int, isRepeat: Bool = false, block: @escaping () -> Void) {
// 默認在主隊列中調度使用
timer = DispatchSource.makeTimerSource()
var timeType: DispatchTimeInterval = .never
if isRepeat {
timeType = .seconds(interval)
}
timer?.schedule(deadline: DispatchTime.now(), repeating: timeType, leeway: .nanoseconds(1))
timer?.setEventHandler {
DispatchQueue.main.async {
// 執行任務
block()
}
}
timer?.setRegistrationHandler(handler: {
DispatchQueue.main.async {
// Timer開始工作了
}
})
// timer?.activate()
timer?.resume()
}
// 線程設置CGD定時器
// ztimer 對應線程
// interval 時間
// isRepeat 是否循環
// block 執行返回
func setTimeInterval(ztimer: DispatchSourceTimer, interval: Int, isRepeat: Bool = false, block: @escaping () -> Void) {
timer = ztimer
var timeType: DispatchTimeInterval = .never
if isRepeat {
timeType = .seconds(interval)
}
timer?.schedule(deadline: DispatchTime.now(), repeating: timeType, leeway: .nanoseconds(1))
timer?.setEventHandler {
DispatchQueue.main.async {
// 執行任務
block()
}
}
timer?.setRegistrationHandler(handler: {
DispatchQueue.main.async {
// Timer開始工作了
}
})
// timer?.activate()
timer?.resume()
}
}
參考文章
https://blog.csdn.net/guoyongming925/article/details/110224064