Swift2まで
メインスレッド上からdispatch_sync
でメインキューに突っ込もうとするとデッドロックしてしまうことがあり、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
クラスを使った書き方に変わったのもあり、以下のように書き換えたらいいんじゃないかなと思います。
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に書けるぜ!とかあったら教えてください...!