I. はじめに
iOSでよく使われるタイマーには、NSTimer、CADisplayLink、GCDタイマーがあります。今回は主に3種類のタイマーの使い方と、グローバルタイマーのカプセル化について紹介します。主な処理としては、タイマーの知識、マルチスレッド、ロック、メッセージ転送などがあります。
第二に、使用
タイマー
なぜなら、selfはタイマーを強く参照し、タイマーはターゲットを強く参照するからです。
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(test), userInfo: nil, repeats: true)
番目のこの方法は循環参照の問題を解決してくれますが、タイマーをランループに追加しないと正しく動作しないので注意が必要です。
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {[weak self] (timer) in
self?.test()
})
self.timer = Timer.init(timeInterval: 1, repeats: true, block: {[weak self] (timer) in
guard let self = self else { return }
self.test()
RunLoop.main.add(timer, forMode: RunLoop.Mode.common)
})
self.timer?.fire()
CADisplayLink
初期化メソッド
self.link = CADisplayLink.init(target: self, selector: #selector(test))
self.link?.preferredFramesPerSecond = 1
self.link?.add(to: RunLoop.current, forMode: .default)
CADisplayLinkを使用する場合、タイマーと同じ問題があることに注意してください。
GCD タイマ
// タイマー作成
let queue = DispatchQueue.global()
self.gcdTimer = DispatchSource.makeTimerSource(queue: queue)
// self.gcdTimer?.schedule(wallDeadline: DispatchWallTime.now(), repeating: 1)
self.gcdTimer?.schedule(deadline: DispatchTime.now(), repeating: 1)
self.gcdTimer?.setEventHandler { [weak self] in
self?.test()
}
self.gcdTimer?.resume()
GCDタイマーを使用するには、gcdTimerを保持するメンバ変数が必要です。
第三に、結論の比較
- タイマーとCADisplayLinkの使用はrunloopの実行に依存し、runloopは同時に他のタスクを実行するため、結果としてタイムリーではなくなります。また、不適切な使用はメモリ・リークを引き起こしやすくなります。
- GCDはシステム・カーネルに直接リンクされており、ランループに依存しません。
グローバルタイマーカプセル化
インターフェースパラメータ設計
- グローバルタイマーは複数回実行される可能性があり、一意に識別する必要があります。
- タイマー開始時間の設定
- タイマー間隔の設定
- タイマーを繰り返すかどうかの設定
- タイマーを非同期に実行するかどうかを設定します。
- コールバック
class TimerManager: NSObject {
/// 各キーは、一意のタイマー、ソリッド辞書ストレージに対応する
private var timers = NSMutableDictionary()
/// これは、同時にマルチスレッドの読み取りと書き込みが含まれるため、エラーを避けるために、データの変更の実装では、操作をロックする必要がある
private let semaphore = DispatchSemaphore.init(value: 1)
static var instance: TimerManager {
struct Static {
static let instance: TimerManager = TimerManager()
}
return Static.instance
}
/// カウントダウンメッセージ転送
/// - Parameters:
/// - timerKey: カウントダウンキーは、一意であることを確認する必要がある
/// - targat: メッセージ転送
/// - selector:
/// - start: 開始時刻
/// - interval: インターバル
/// - repeats: 繰り返すかどうか
/// - async: 非同期かどうか
func schedule(timerKey: String, targat: NSObject, selector: Selector, start: DispatchTime = .now(), interval: TimeInterval = 1, repeats: Bool = true, async: Bool = true) {
self.schedule(timerKey: timerKey, start: start, interval: interval, repeats: repeats, async: async) { [weak targat] in
/// targatがセレクタに応答できるかどうかをチェックする。
if targat?.responds(to: selector) ?? false {
targat?.perform(selector)
}
}
}
/// カウントダウンブロック
/// - Parameters:
/// - timerKey: カウントダウンキーは、一意であることを確認する必要がある
/// - start: 開始時刻
/// - interval: インターバル
/// - repeats: 繰り返すかどうか
/// - async: 非同期かどうか
/// - eventHandle:
func schedule(timerKey: String, start: DispatchTime = .now(), interval: TimeInterval = 1, repeats: Bool = true, async: Bool = true, eventHandle: @escaping (() -> Void)) {
guard !timerKey.isEmpty || start.rawValue <= 0 || interval <= 0 else {
return
}
let timerQueue = async ? DispatchQueue.global() : DispatchQueue.main
let timer = DispatchSource.makeTimerSource(queue: timerQueue)
semaphore.wait()
timers[timerKey] = timer
semaphore.signal()
timer.schedule(deadline: start, repeating: interval)
timer.setEventHandler { [weak self] in
eventHandle()
if !repeats {
self?.cancelTask(timerKey: timerKey)
}
}
timer.resume()
}
/// タイマーキャンセル
/// - Parameter timerKey: タイマーID
func cancelTask(timerKey: String) {
guard !timerKey.isEmpty else {
return
}
guard let timer = timers[timerKey] as? DispatchSourceTimer else {
return
}
timer.cancel()
semaphore.wait()
timers.removeObject(forKey: timerKey)
semaphore.signal()
}
}
インタフェースのパラメータ設計
/// メッセージ転送
TimerManager.instance.schedule(timerKey: self.theClassName, targat: self, selector: #selector(test))
/// block
TimerManager.instance.schedule(timerKey: self.theClassName) { [weak self] in
self?.test()
}
グローバルタイマーは、アプリケーションがバックグラウンドに退避し、時間間隔で戻ってくることを考慮していません。
、通知を聞き、打ち上げ前後の時間差を比較し、データソースに変更を加えます。
、毎回NSDateを保存する時間のデータ処理時間()、データ処理の次の実行では、まず保存されたNSDateと現在の時差を比較し、データ処理インチコードは次のとおりです。
@objc func test() {
/// currentCountが徐々に減っている場合は、ここで判断する必要がある。 0より小さい場合は、タイマーメソッドの破棄を実行する。
// if currentCount <= 0 {
// TimerManager.instance.cancelTask(timerKey: self.theClassName)
// }
if CacheManager.instance.hasCachedValue(with: self.theClassName) {
if let cacheDate = CacheManager.instance.valueWithCache(key: self.theClassName) as? NSDate {
if cacheDate.timeIntervalSinceNow < -2 {
///時差は負の数であるため、カウントダウンであれば時差を加算し、その逆であれば時差を減算する。
self.currentCount -= Int(cacheDate.timeIntervalSinceNow) + 1
}
}
}
CacheManager.instance.cacheData(NSDate(), withKey: self.theClassName)
currentCount += 1
print(currentCount)
}





