Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

アプリ画面のライフサイクル管理をAppDelegateからSceneDelegateへ移行した話

こんにちは、Studyplus事業部モバイルクライアントグループの明渡です。 最近、当ブログの当番がチーム単位から個人単位へ変更になりました。 ひとまずブログ執筆をご無沙汰していた順に回るのですが、自分は昨年iOSDCのLT登壇内容まとめ記事以来でした。

iOS 13におけるSiri Shortcuts 最小実装+α スライド書き起こしと補足 - Studyplus Engineering Blog

今年も最近リリースした機能をネタにプロポーザルを出してみたので、もし通ったらいろいろ頑張ります。

2021年5月、Studyplus iOS版アプリにて画面のライフサイクル管理をするクラスを移行しました。 具体的には、UIApplicationDelegate準拠のAppDelegateから、UIWindowSceneDelegate準拠のSceneDelegateへ乗り換えました。

Studyplusでは今年3月にiOS 12のサポートを終了しました。 これにより、iOS 12向けの分岐を混在させる必要がなくなり、綺麗に移行できる準備が整ったので、一気に対応してしまいました。

今回は、移行をスムーズにするため行なった事前リファクタリングと、StudyplusにてAppDelegateからSceneDelegateへ移行した処理について紹介します。

AppDelegateとSceneDelegateとは

iOSアプリ開発でおなじみ、プロジェクト作成時に1アプリにつき1クラスずつテンプレートが生成されるアプリのライフサイクルを管理するクラスです。

iOS 12までのプロジェクトはAppDelegateのみ、iOS 13以降およびiPad OS 13以降のアプリではAppDelegateとSceneDelegateが共に作成されます。

SceneDelegateは、1アプリを複数ウィンドウで操作を可能とするMultiple Windows対応に必須です。 Studyplusでは現状、Multiple Windows対応の肝である複数画面での操作受付(Info.plistにてSupports multiple windowsの値)はOFFにしています。 実際に複数画面での操作に対応するにはどの画面の情報をどのように保持するか精査する必要があるためで、近い将来きちんと対応したいところです。

事前リファクタリング

SceneDelegate移行の前に、リファクタリングとしてCoordinatorパターンを導入しました。

speakerdeck.com

導入にあたり、スライド、および書籍のiOSアプリ設計パターン入門を参考にしました。 一番の目的としては、長年処理を継ぎ足し続けて混沌としていたAppDelegateから画面遷移処理を剥がし、安全にSceneDelegateへの移行をするためです。

今回適用したのはAppDeleagteにて生成していた画面のみですが、今後作成する新機能および既存の他画面も準拠させる予定です。 GitHubのProjectsへボードを作り、アプリプロジェクト内のファイル名末尾ViewController.swiftで検索をかけて全部チケットを起票しておきました。

なお、こうして起票したチケットは、各エンジニアが手が空いた時に着手中(In progress)にステータスを切り替えて対応を進めているため、対応するクラスが重複する心配がありません。 また、1つのテーマに関するチケット群がどの程度対応完了したかの進捗が分かりやすくなるので、対象が膨大なリファクタリング等を進める際に大変重宝しています。

現状のStudyplusでは向こう数年UIKitベースがメインになることが見えていたので迷わずCoordinatorパターンを導入しました。 しかしながら、今後SwiftUI化を積極的に進めていく場合には課題がある設計なので、導入は慎重に検討することをお勧めします。

AppDelegateからSceneDelegateへ移行した処理

アプリの画面生成・遷移処理

SceneDelegateは画面(UIWindow)のライフサイクルを管理するクラスのため、画面生成および遷移を伴う処理は軒並み移行しました。

具体的には以下の通りです。

  • application(_:didFinishLaunchingWithOptions:)内の画面生成処理
    • 通常のフローでアプリ起動時に初期生成する画面
  • application(_:continue:restorationHandler:)内の画面生成・遷移処理
    • Universal Links、Siriショートカットを経由してアプリが起動された際に生成・遷移する画面
  • application(_:open:options:)内の画面生成・遷移処理
    • カスタムURLスキーマを経由してアプリが起動された際に生成・遷移する画面
  • application(_:didReceiveRemoteNotification:fetchCompletionHandler:)内の画面生成・遷移処理
    • プッシュ通知を押下してアプリが起動された際に生成・遷移する画面

Studyplusでは、諸事情により通常のフロー以外でアプリが起動された際、すでに起動済みだった場合は前の画面は保ったまま画面遷移しています。

AppDelegateでは通常フロー以外の起動時に特定のメソッドを同じ順番で必ず通る保証がないため、同じメソッド内に起動時と起動済みの分岐をまとめて定義が必要でした。

SceneDelegateでは通常フロー以外のアプリ起動時にも必ずscene(_:willConnectTo:options:)を通るようになりました。 これにより、アプリ起動時or起動済み時の判定をscene(_:willConnectTo:options:)から呼び出されたかそうでないかで綺麗に分けられるようになりました。

Studyplusではプッシュ通知に関する処理をまとめて定義している箇所に影響があり、それぞれのケースで必要な処理を綺麗に分離できて嬉しかったです。

画面が生成、破棄された都度必要な処理

それぞれ、以下のタイミングで呼ばれていた処理はほぼ丸ごと移行しました。 すべてApple Developerのドキュメントへ移行を推奨する旨が記載されているメソッドです。

  • applicationWillEnterForeground(_:)sceneWillEnterForeground(_:)
  • applicationDidBecomeActive(_:)sceneDidBecomeActive(_:)
  • applicationWillResignActive(_:)sceneWillResignActive(_:)
  • applicationDidEnterBackground(_:)sceneDidEnterBackground(_:)

おまけ:AppDelegateからSceneDelegateへ移行しなかった処理

アプリのプロセス(UIApplication)のライフサイクル毎に行う処理はAppDelegateから移動させませんでした。

具体的には以下の通りです。

  • application(_:didFinishLaunchingWithOptions:)内のアプリ起動時1回のみ呼ぶ必要がある処理
    • 課金トランザクションの検知準備や、Firebaseなど利用しているライブラリの初期化処理など
  • applicationWillTerminate(_:)内のアプリ終了時に処理
    • 課金トランザクションの管理終了や、次回アプリ起動時に保ちたいデータの保存など

さいごに

以上、Studyplus iOS版におけるSceneDelegate移行対応のお話でした。

事前準備のほうに数倍時間がかかったので、普段からAppDelegateが綺麗に整理されていればあっという間に移行できるでしょう。

幸い、移行による目立ったデグレは現在まで発生しておらず、つつがなく完了できたといえます。 Coordinatorパターン適用前のAppDelegateでは、そのまま移行すると何かかしらデグレさせてしまいそうで怖かったです。

Coordinatorパターンはこれから全画面で準拠していくのが目標なので、進むにつれ画面遷移処理が疎結合になって見やすくなり、テストしやすくなっていくのが楽しみです!