GCD
Swift
Swift3.0
DispatchQueue
DispatchSemaphore

SwiftのGCDで最新のキューだけを処理し続ける

重たい画像処理のパラメータをGUI Sliderで操作しつつ結果をプレビューしたいとき、
画像処理にかかる時間よりもGUIからイベントが送られて来る間隔の方が圧倒的に短くて

  • 同期処理だとUIがカクカクする
  • 非同期直列処理だとプレビューへの反映が遅い
  • 非同期並列処理だとプレビューが最新であることが保証されない

という問題にぶちあたった。

UIをストレスなく操作させつつなるべく最新の結果をプレビューできるようにするためには

  • 非同期処理
  • 処理中に溜まっていくタスクは最新のもののみ保持して残りは捨てる

という実装がよさそう。
ということでできたものがこちらです。

Swift3.0
class ViewController: UIViewController {
    // 同時実行数を1に制限するためのセマフォ
    let semaphore = DispatchSemaphore(value:1)
    let queue = DispatchQueue(label: "imageproc", qos:.userInteractive)
    var workItem : DispatchWorkItem?

    // このハンドラがえげつない回数呼ばれる
    @IBAction func imageProc(_ sender: Any) {
        // 前回追加したタスクが未処理の場合はキャンセルされる
        workItem?.cancel()

        workItem = DispatchWorkItem {
            // 処理中のタスクがある場合は待つ
            self.semaphore.wait()
            self.imageProcImpl()
            // プレビューへの反映はメインスレッドで
            DispatchQueue.main.async {
                self.imageProcAfter()
                // プレビューへの反映が終わってからセマフォを解放
                self.semaphore.signal()
            }
        }
        // キューに追加
        queue.async(execute: workItem!)
    }
    func imageProcImpl() {
        // 重たい画像処理
    }
    func imageProcAfter() {
        // プレビューに反映
    }
}