問題はありませんdispatch_get_main_queue()
。(別のキューを使用すると、同じ動作に気付くでしょう。) 問題は にありdispatch_after()
ます。
を使用すると、 /dispatch_after
の 10% の余裕を持ったディスパッチ タイマーが作成されます。Apple github libdispatch ソースを参照してください。最終的な影響として、これらのタイマー ( ± 10% ) がオーバーラップすると、それらの合体が開始される可能性があります。それらが合体すると、それらは「まとまった」方法で発砲するように見えます。それらの束はすぐに次々と発砲し、次の束に到達する前に少し遅れて発砲します。start
when
start
leeway
いくつかの解決策がありますが、すべて一連のdispatch_after
呼び出しを廃止する必要があります。
DispatchSource.TimerFlag.strict
合体を強制的に無効にして、手動でタイマーを作成できます。
let group = DispatchGroup()
let queue = DispatchQueue.main
let start = CACurrentMediaTime()
os_log("start")
for i in 0 ..< 1000 {
group.enter()
let timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue) // use `.strict` to avoid coalescing
timer.setEventHandler {
timer.cancel() // reference timer so it has strong reference until the handler is called
os_log("%d", i)
group.leave()
}
timer.schedule(deadline: .now() + Double(i) * 0.1)
timer.resume()
}
group.notify(queue: .main) {
let elapsed = CACurrentMediaTime() - start
os_log("all done %.1f", elapsed)
}
個人的には、クロージャー内への参照は嫌いtimer
ですが、タイマーが起動するまで強い参照を保持する必要があり、タイマーがキャンセル/終了すると、GCD タイマーはブロックを解放します (強い参照サイクルを回避します)。これは洗練されていないソリューションです、IMHO。
0.1 秒ごとに起動する単一の繰り返しタイマーをスケジュールする方が効率的です。
var timer: DispatchSourceTimer? // note this is property to make sure we keep strong reference
func startTimer() {
let queue = DispatchQueue.main
let start = CACurrentMediaTime()
var counter = 0
// Do some async tasks
timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue)
timer!.setEventHandler { [weak self] in
guard counter < 1000 else {
self?.timer?.cancel()
self?.timer = nil
let elapsed = CACurrentMediaTime() - start
os_log("all done %.1f", elapsed)
return
}
os_log("%d", counter)
counter += 1
}
timer!.schedule(deadline: .now(), repeating: 0.05)
timer!.resume()
}
これにより、合体の問題が解決されるだけでなく、より効率的になります。
Swift 2.3 レンディションについては、この回答の以前のバージョンを参照してください。