前回こんな記事を書きました。
Swift 世代の排他制御
http://qiita.com/codelynx/items/0ecd28c8a7da0a0e42b5
この記事では、以下のような排他制御の書き方を紹介しました。NSLock
を使えば、複数のスレッドからクリティカルなデータの同時更新を防げるという話でした。具体的には、NSLock
を lock()
してから unlock()
するまでは、他のスレッドは待たされるので、atomicity が保証できるという話です。しかも、defer
文を使えば、スコープを抜けた時に、確実に実行されるので、途中でいくつもの return
文があるようなパターンでも、実行し忘れがなく、安全という事です。
class MyObject {
let lock = NSLock()
func update1() {
self.lock.lock()
defer { self.lock.unlock() }
// update some critical resources
}
func update2() {
self.lock.lock()
defer { self.lock.unlock() }
// update some critical resources
}
}
さて、先日、通信処理が終わるまではロックしたいという場合に遭遇しました。通信の response が帰ってきた所の closure に unlock()
を入れると、lock()
と unlock()
は同じスレッドでやりなさいと怒られてしまいます。そこで、仕方がないので、GCD の Semaphore を使ってみる事にしました。Swift 2.x からかなり書き方は変わっています。今回の使い方の場合は、DispatchSemaphore
の value
を 1
にします。
class MyObject {
let semaphore = DispatchSemaphore(value: 1) // "1"
func update1() {
semaphore.wait()
defer { semaphore.signal() }
// update some resources
}
func update2() {
semaphore.wait()
defer { semaphore.signal() }
// update some resources
}
}
もし、通信の request から response までの間を排他制御したい場合には、これらのメソッドは決して main thread で呼び出してはいけません。なぜなら通信はミリ秒単位で完了する事を保証している訳ではないので、UI がブロックされてしまう可能性があるからです。よって、そんな場合には 以下のように assert(!Thread.isMainThread)
の一文を挟んでもいいのかもしれません。
class MyObject {
let semaphore = DispatchSemaphore(value: 1)
let session = URLSession(configuration: URLSessionConfiguration.default)
func update1() {
assert(!Thread.isMainThread)
semaphore.wait()
let url = URL(string: "https://...")!
let task = self.session.dataTask(with: url) { data, response, error in
defer { self.semaphore.signal() }
// update some critical resources
}
task.resume()
}
func update2() {
assert(!Thread.isMainThread)
semaphore.wait()
let url = URL(string: "https://...")!
let task = self.session.dataTask(with: url) { data, response, error in
defer { self.semaphore.signal() }
// update some critical resources
}
task.resume()
}
}
コードは gist からも入手できます。
• 通常版
https://gist.github.com/codelynx/dcff25a02bf0a744473914ce18fc86b7
• 通信版
https://gist.github.com/codelynx/f004e4046382c4751aa9f35fdf82376f
[環境に関する表記]
Xcode Version 8.1 (8B62)
Apple Swift version 3.0.1 (swiftlang-800.0.58.6 clang-800.0.42.1)