~iOS13でURLSessionのcompletionHandlerが実行されない問題への対応
久しぶりに面白いバグに遭遇した.
iOS14ではURLSession.dataTask(with:completionHandler:)のcompletionHandler
が実行されるが, それ以前のOSでは実行されないというものだ.
それも全ての箇所ではなく, 1箇所だけ何故か常に実行されない.
OperationQueue.mainでOperationが実行中は他のOperationが実行されなくなる
今回問題が発生したコードは以下のような構造をしていた. もちろん, 実際のコードはこんなシンプルではなかったが.
このコードのAsyncBlockOperation
は引数のblockを実行するまで完了とみなさないBlockOperationである.
おおよそのコードはここから確認できるが, 本質ではないので詳細は割愛する.
ここでポイントとなるのは, AsyncBlockOperation
とURLSession
のキューがともにmainQueue
であるという点である.
OperationQueue
はmaxConcurrentOperationCountで指定した数までOperation
を同時実行でき, 閾値に達すると実行中のOperation
が終了するまで待つという挙動をする.
OperationQueue()
で作成した場合はデフォルトで-1
(制限なし)だが, mainQueue
は1
になっている.
つまり, このコードのOperation
以下の2つでデッドロック状態になっているということだ.
AsyncBlockOperation
の完了にはURLSessionDataTask
の実行が完了する必要がありURLSessionDataTask
の実行が完了する為には,AsyncBlockOperation
が完了している必要がある
これだけなら問題は単純なのだが, この問題はそんな簡単ではなかった. iOS14では動くのである.
apple/swift-corelibs-foundationを見ても, addOperation
しているのでスタックトレースから見ることにした.
iOS14からはURLSessionのcompletionHandlerの実行形式が変わったようだ
iOS13 | iOS14 |
---|---|
これはともに, completionHandler
の1行目で止めたときのスタックトレースである.
これを見ると, iOS13では-[NSBlockOperation main]
が登場しているのに対し, iOS14ではdispatch_async
で実行されているのが分かる. どうやら挙動が変わったらしい.
なお, テストでこの動作を再現しようとしたところ, iOS14でもiOS13と同様のスタックトレースになったので単純にOSによる差ではないようで, 謎が深まるばかりだった.
取り敢えず, 非同期のOperation
を使う時はOperationQueue.main
を避けるようにしよう.
参考
この問題を試すためのコードは以下においてある.
記事が気に入ったらチップを送ることができます!
You can give me a cup of coffee :)
Kyash ID: soranoba
Amazon: Wish List
GitHub Sponsor: github.com/sponsors/soranoba
PayPal.Me: paypal.me/soranoba
(Updated: )