- このエントリはSwift 1.0の時に初版を書きました
- このエントリはSwift version 2.1 / Xcode 7.1.1 で動作確認済みです
Swift Book にはスレッドや同期(synchronization)への言及がないからどうしたものかなと思っていたのだけど、objcにAPIをつかえば実現はできそうだ。
参考: What is the Swift equivalent to Objective-C's “@synchronized”?
demo: https://github.com/gfx/Swift-MultiThreading
objc_sync_enter() / objc_sync_exit() を使う
これらはobjcの低レベルAPIで、@synchronized
ブロックの実装のためにコンパイラが使う関数だけど、Swiftからも呼び出せる。
このままだと使いにくいのでRAIIパターンをつかって使いやすくしよう。
class AutoSync {
let object : AnyObject
init(_ obj : AnyObject) {
object = obj
objc_sync_enter(object)
}
deinit {
objc_sync_exit(object)
}
}
これを使うときは単にインスタンスを作成するだけでよく、スコープの終わりになると自動的に解放される。
func f() {
let lock = AutoSync(self) // lockは自動的に解放される
self.value++
}
Swift 2.1 追記
これをこのまま使うと、 let lock
で「lock
は使われないので _
に変えろ」といわれる。しかし、その通りに書き換えるとスレッドがロックされなくなる。最適化によってオブジェクトが消えるためと思われるが、RAIIパターンがまともに使えなくなるのでこれはコンパイラのバグであろう。
GCDのserial queueを使う
これは『Effective Objective C』でも紹介されている方法で、うまくつかうと @synchronized
や NSLock よりも高速にできるらしい。ただしXcode 6.3の時点では、最適化(-O)した状態だとどちらも同程度の速度だったので、速度の面でどちらにすべきということはない。
func f() {
struct _IncrementWithSyncQueue {
static let s = dispatch_queue_create("com.github.gfx.ios.test.increment_with_sync_queue", DISPATCH_QUEUE_SERIAL)
}
dispatch_sync(_IncrementWithSyncQueue.s) {
self.value += 1
}
}
いずれにせよ、Objective-C時代と大きく変わるわけではなさそうだ。