Swift3でDispatchQueue.main.syncを安全に呼び出す


Swift2まで

メインスレッド上からdispatch_syncでメインキューに突っ込もうとするとデッドロックしてしまうことがあり、Swift2まではだいたい以下のような書き方で対応していたと思います。

swift2まで
public func dispatch_sync_main_safe(block: dispatch_block_t) {
    if NSThread.isMainThread() {
        block()
    } else {
        dispatch_sync(dispatch_get_main_queue(), block)
    }
}

Swift3から

Swift3ではdispatch_xxxという関数での使用の仕方から、DispatchQueueクラスを使った書き方に変わったのもあり、以下のように書き換えたらいいんじゃないかなと思います。

swift3から
extension DispatchQueue {
    class func mainSyncSafe(execute work: () -> Void) {
        if Thread.isMainThread {
            work()
        } else {
            DispatchQueue.main.sync(execute: work)
        }
    }

    class func mainSyncSafe<T>(execute work: () throws -> T) rethrows -> T {
        if Thread.isMainThread {
            return try work()
        } else {
            return try DispatchQueue.main.sync(execute: work)
        }
    }    
}

使用方法はこんな感じ。

DispatchQueue.mainSyncSafe {
    // ...
}

mainSyncSafeってのがう〜んって場合はお好みでmain_sync_safeなり変えてあげると良いんじゃないかと思います。

変更点

DispatchQueueの sync 関数が大きく分けて2通りある

よく見てみると、性質の異なる sync 関数が2つあります。(正確には2つではないけど他は一旦割愛)

public func sync(execute block: () -> Swift.Void)
public func sync<T>(execute work: () throws -> T) rethrows -> T

片方はいつもどおりの sync 関数ですが、もう片方は 例外を投げられるようになっている ことと、 workのclosureの結果から 返り値を受け取ることができる ところが差分となります。
なのでどちらにも対応できるように準備してあげます。

NSThread -> Thread

ここも地味な変化です。

class funcにした理由

普通のfunc syncSafe(...)として定義してしまうと、以下のようにグローバルキューに対しても呼べてしまう問題が発生するので、若干関数名がダサいとは思いつつも、class funcにすることにしました。(今回はあくまでも、メインスレッド上からdispatch_syncでメインキューに突っ込もうとするとデッドロックが起きてしまうのを防ぐ呼び方として定義したかったのが強いからです。)

// ok!
DispatchQueue.main.syncSafe {
    // ....
}

// ????
DispatchQueue.global().syncSafe {
    // ....
}





こっちの方がcoolに書けるぜ!とかあったら教えてください...!