【Swift】iOS 16で画面回転禁止処理を行っている箇所で無限ループが発生しクラッシュする

iOS, Swift

はじめに

こんにちは、iOSアプリ開発グループの深山です。
私が担当しているアプリでは、画面の回転制御を行うために、最前面のViewを取得する処理が行われています。あるとき、開発中にその処理が無限ループする不具合に遭遇しました。
この記事ではその事象内容と解決方法を紹介します。

動作環境

  • Xcode 14.1
  • iOS 16.1.2
  • Swift 5.7.1

最前面のUIViewControllerの取得

私の担当アプリでは、デザインの関係上、一部の画面でのみ画面回転を許可しています。
画面回転時に参照される supportedInterfaceOrientationsをオーバーライドすることで、この制御を行っています。

このアプリでは UITabBarControllerを使用しています。
画面回転制御を行うために、回転させるかどうかをルートとなるタブバークラスでは判断せず、以下のように、表示されている各Viewに移譲する形となっています。

UIApplication.topViewController()は以下のようになっています。(参考)

各Viewクラスでは、 supportedInterfaceOrientationsをオーバーライドし、各画面での画面回転の有無や向きを指定して制御を行います。

上記のように、ルートのタブバークラスが最前面のViewで指定されている回転制御内容を取得し、制御内容を自身に反映させることで、各画面ごとの回転制御を行っています。

発生したクラッシュ

今回私が遭遇したのは、タブバークラスでの次のエラーによるクラッシュです。

crash.png

前項で紹介した手法での画面回転制御時に、iOS 16環境でのみ EXC_BAD_ACCESSでクラッシュする事象が発生しました。

こちらは、Appレビューのダイアログを表示したときに発生したエラーになります。

このエラー状況を調査をしてみたところ、ダイアログ表示時に、該当箇所が繰り返し呼び出され続けていることがわかりました。

原因と解決方法

エラー箇所にある最前面View取得処理の独自メソッド topViewController()内に問題があると考え、Appレビューのダイアログが表示時に topViewController()で返却されている UIViewControllerがなにかを調べてみたところ、 SKStoreReviewViewControllerというクラスであることがわかりました。

topViewController_response.png

これはAppleが公開している UIViewControllerではく、Appleが提供する StoreKitフレームワークの非公開なクラスのようです。
このことから、 SKStoreReviewViewController内の supportedInterfaceOrientationsを返却する処理と今回発生したクラッシュがなにか関係がありそうだということがわかりました。

topViewController()にて、Appレビューのダイアログに該当する SKStoreReviewViewControllerが返却された際は、そのViewの supportedInterfaceOrientationsを使用しないようにすることで、処理が繰り返し呼び出されるのを防げるのではないかと考えました。

しかし、 SKStoreReviewViewControllerは非公開なクラスであるため、以下のように isを使ってクラスの等価性を比較することはできません。

solution_error.png

そこで、別の解決方法を検討したところ、以前 UIAlertController表示時にも同様の事象が発生したことを思い出しました。
このことから、 SKStoreReviewViewControllerに限らず、内部でどのような処理が行われているかわからないクラスを参照することがエラーの原因になるのではないかと推測しました。

このことを踏まえ、以下のような対応を行いました。

getTopViewController()は、最前面の UIViewControllerを取得する際、特定の UIViewController参照時に nilを返却する対応を含んだメソッドです。

『アプリ内で独自に作成した UIViewControllerクラス以外 = Apple等が提供するフレームワーク上の UIViewControllerクラス』
と見なし、こちらを取得したときに nilを返却することで適切にハンドリングが行えるようにしました。

この対応により、無事クラッシュを起こすことなく、Appレビューのダイアログを表示することができました。

補足

次のように、文字列比較で、任意のクラスに対してハンドリングを行うこともできます。

しかしこの方法だと、対象のクラスが複数ある場合・ハンドリングすべきクラスを新たに発見した場合に、条件文を都度追加していく必要があり、コードの見通しも悪くなります。

前項で紹介した実装であれば、今回の事象が起こり得る複数のクラスに対する処理を、簡潔に記述することができるので、そちらを用いるのが良いでしょう。

まとめ

画面回転制御時、独自に作成した UIViewControllerクラス以外の表示が原因で不具合が発生する可能性があることがわかりました。
supportedInterfaceOrientationsのオーバーライドを用いて画面回転を制御する場合は、importしたフレームワーク内のクラスのような、内部でどのような処理が行われているかわからないクラスを参照する可能性を考慮して適切にハンドリングをしましょう。

参考文献

iOS, Swift