SwiftUI からアプリ開発始めた方を想定した@UIApplicationDelegateAdapter を題材とした読み物。
1) SwiftUIとそれ以外の世界
SwiftUIは宣言的にAppleのOS上で動作するUIを記述できるフレームワークで、Webプログラミングにおける記述したコードをWebブラウザがレンダリングしている動作に近い。クリーンアーキテクチャの観点から言うとReduxの一部、開発環境としてはReactJSを理解していると府に落ちるところはあるかもしれない。
2020年におけるSwiftUIは、オブジェクトとイベント、メッセージ送信の世界にも片足を突っ込んでいることを忘れてはならない。
オブジェクトとイベント、メッセージ送信の世界とは、SwiftUIより前のUI構築手段や、各種フレームワークにおいて提供されつづけていた機能の集合体で、高レベルではオブジェクトの考え方にそって利用しやすくなっており、それほど高度では無い場合は構造体と関数で構成されるなど用途も歴史もふるく膨大な数がある。
SwiftUIを使用する場合もオブジェクトだったり何かしらの関数だったりを利用しなくてはならない状況は変化しない(ハードウェアの機能にアクセスするにも何かしらのフレームワークは必要になる)。
2) レンダリングのコスト
SwiftUIはWebブラウザがページをレンダリングするように、UIをレンダリングするレンダラーが存在する。
レンダラーの最終的な出力は、動作環境ごと(iOS,iPadOS,macOS,watchOS,tvOS)に最適化されたUIコントロール郡にはなるが最終レンダリングがあるということはその途中のレンダリング状態も存在すると考えた方が自然だろう。
途中のレンダリング状態はどうなってしまうのか?おそらくは破棄される(SwiftUIのレンダリング機能内部なので実際そうなのかはAppleの資料を読む必要がある)。
2020年においてはハードウェア上の単純なメモリコピー処理のコストが低いのでSwiftUIじょうで記述できる定義はメモリレイアウトが実行時に決まっているStruct(構造体)なので途中状態で途中破棄や、際レンダリングのコストは(UIを構築する処理よりは)低いと。
3) レンダリングとオブジェクトの相性の悪さ
SwiftUIのレンダリングが素晴らしいとして、なにかしらのフレームワークの機能を組み込む際に、オブジェクトの形で提供されていない場合は最終レンダリングが完成したタイミングでオブジェクトが生成される、もしくは初期化されないと複数初期化コードが呼ばれたり、使われないオブジェクトの初期化が数回起きるなどの問題が発生する。
具体的には、SwiftUI 1の頃にカスタムViewを作成する際に複数初期化が呼ばれてしまう問題を回避するために内部的にa) NSObject.performSelector(withObject:afterDelay:)/NSObject.cancelPreviousPerformRequests(withTarget:) を使ってdebounce 挙動を実現する。b) View.onAppear、View.onDisappear でレンダリング完了を検知 といったaはiOS開発者のノウハウの一部、bはSwiftUIを使うための苦肉の策として編み出した対策が必要だった。
4) SwiftUI 2nd major releaseでの改善
SwiftUI 1までは最終レンダリングでオブジェクトが生成される仕組みが用意されなかったがSwiftUI 2では@StateObjectとして用意されている。フレームワークオブジェクトを使う場合は、ObservableObject の派生したクラスをSwiftUI 上で、@StateObject 宣言することで最終レンダリングタイミングでオブジェクトが生成される。
2020年7月27日(月)訂正。@StateObject を指定するとObservableObject の派生したクラスのライフサイクルはレンダリング結果のオブジェクトのライフサイクルと同じになります。
派生クラスでフレームワークの機能を呼び出すといったケースは上記のパターンで問題ない。
5) UIApplicationDelegate
SwiftUIを使おうとしても、SwiftUI以前の機能にアクセスは避けられない。SwiftUIはレンダリングベースで表示を最終決定するのでオブジェクト寿命と幾分相性が悪いが、SwiftUI 2で改善(StateObject)されている。
この改善によりdelegate(代理)に対応できる。
Delegateを端的に説明するとある規約(protocol)に準拠したオブジェクトをイベントを受け取りたいオブジェクトのと特定のプロパティに登録する。登録後はイベントを受け取りたいオブジェクトが(protocol)に準拠したオブジェクトのメソッドを呼び出す。まさしく代理で、イベントを受け取りたいオブジェクトがアプリ内で1つしか存在しないオブジェクトだったり、データソースを要求するUIなど幅広く利用されてきた。
delegate(代理)の歴史を紐解くと、iOSアプリが一般開発者向けに公開されたiPhone SDKのころからオブジェクトのイベント処理の一種類として存在してきた。他のイベント通知手段もあるのでiOS SDK開発はイベント種類の習得と用途を把握する必要があり他の開発環境に比べると異質ではあった。
Delegateの中でもUIApplicationDelegateは重要でUIApplicationDelegate はiOSアプリ開発者ならよく目にするprotocolでアプリケーションに外部から来たイベントを処理するのに使用される。特徴としては、
歴史が古い コード数は多い 非奨励メソッド多数 メソッドのコメント多数
など読み解くのに難儀するし、UIApplicationDelegate を周知していることがiOSアプリ開発者の熟練度にも繋がるぐらい重要なprotocolだった。SwiftUI 2では開発者からはAppを記述する必要はあるが、代理の考え方やUIApplicationDelegateは一見して隠蔽されている。
隠蔽はされているがアプリケーションが外部イベントに反応しないわけにはいかない、しかもUIApplicationDelegate はアプリケーションに1つしかないUIApplication.shared.delegateプロパティに直接登録しなくてはならない制約がある。
複数のオブジェクトがアプリケーションのイベントを登録する場合、手動でUIApplication.shared.delegateを探して登録するのか?
6) @UIApplicationDelegateAdapter
ここまできてやっと@UIApplicationDelegateAdapter の話となる。@UIApplicationDelegateAdapterは、1つしか存在しないUIApplication.shared.delegateの存在し隠蔽しつつ、アプリケーションライフサイクルに必要なdelegateイベントを取得できるようになる。
プッシュ通知のデバイストークン取得の可否はUIApplicationDelegateなので@UIApplicationDelegateAdapter をSwiftUIアプリ開発を取り扱うタイミングはすぐに訪れるはずだ。
@UIApplicationDelegateAdapter とSwiftUI App Lifecycle については以下のサイトで端的に示されているので参考にしてほしい。
SwiftUI App Lifecycle. @UIApplicationDelegateAdapter | by Ruchish shah | Jul, 2020 | Medium
何をやっているかについて説明すると@UIApplicationDelegateAdapterに指定できるクラスはUIResponder、 UIApplicationDelegateの派生クラス。派生クラスの型を@UIApplicationDelegateAdapterで就職し宣言すること最終レンダリングタイミングで、インスタンス生成およびUIApplication.shared.delegate へ登録まで担ってくれるユーティリティとしてSwiftUIアプリを使う上で覚えておきたい定義である。
参考
SwiftUI App Lifecycle. @UIApplicationDelegateAdapter | by Ruchish shah | Jul, 2020 | Medium