Alamofire(非同期通信)をsemaphoreで同期的に処理できなかった
はじめに
Alamofireでapi通信する際に、同期的に処理したくなったためSemaphoreを使ってやってみたけどうまくいかなかった時のお話です。
Semaphoreとは
Semaphoreは排他制御の仕組みのことで、リソースへのアクセス可能な数のことです。Semaphoreが0の状態では他の者はリソースにアクセスできないのが"排他的"ということかなと勝手に解釈してます。
この機能を用いることで、非同期処理が終わるのを待って同期的に実行することが可能になります。
詳細はリンク先を参照してください。
Swift3でのSemaphore
iOSでSemaphoreを使う場合はGCDのDispatchSemaphoreを使います。GCDについてはSwift3のGCD(dispatch_async)
を参考にしてください。
Swift3でSemaphoreを使うサンプルは、下記のようになります。
let semaphore:DispatchSemaphore = DispatchSemaphore(value: 0) Alamofire.request(url, method: .get, parameters: parameters, headers: headers) .validate() .responseJSON { response in semaphore.signal() } semaphore.wait()
しかし、このサンプルではアプリが止まってしまいます。なぜなら
.responseJSONのブロック内と、その外側が同じスレッドで処理されるためらしいです。 つまり、dispatch_semaphore_waitで待機してしまうと、.responseJSONのブロック内も処理が止まってしまい、いつまで経ってもロック解除ができずにデッドロック・・・という事みたいです。
Alamofireでデータ受信が終わるまで待機する方法 より
解決策
Alamofireでデータ受信が終わるまで待機する方法 のように実装します。(あまりすっきりしませんが)
var keepAlive = true //ロックが解除されるまで待つ let runLoop = RunLoop.current Alamofire.request(url, method: .get, parameters: parameters, headers: headers) .validate() .responseJSON { response in semaphore.signal() } while keepAlive && runLoop.run(mode: RunLoopMode.defaultRunLoopMode, before: NSDate(timeIntervalSinceNow: 0.1) as Date) { // 0.1秒毎の処理なので、処理が止まらない }
ちなみにAlamofireの部分をDIspatchQue.globalとかで囲ってみたりしてみましたがダメでした。