てくてくテック

気ままに開発のメモを書いていこうと思います。主にSwiftかと。

Alamofire(非同期通信)をsemaphoreで同期的に処理できなかった

はじめに

Alamofireでapi通信する際に、同期的に処理したくなったためSemaphoreを使ってやってみたけどうまくいかなかった時のお話です。

Semaphoreとは

Semaphoreは排他制御の仕組みのことで、リソースへのアクセス可能な数のことです。Semaphoreが0の状態では他の者はリソースにアクセスできないのが"排他的"ということかなと勝手に解釈してます。
この機能を用いることで、非同期処理が終わるのを待って同期的に実行することが可能になります。 詳細はリンク先を参照してください。

wa3.i-3-i.info

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とかで囲ってみたりしてみましたがダメでした。