見出し画像

思い出ウィジェットをリリースした話 Now In REALITY Tech #117

かむいです。子供たちとの水遊びが最近の日課です。

REALITYのiOSアプリver 24.26.0にて思い出ウィジェット機能をリリースしました。これは昨年の開発合宿で私が発表したテーマであり、今回はリリースに向けて取り組んだことについてお話したいと思います。


以前から導入したかったウィジェット

実はウィジェット機能は一昨年の開発合宿でもテーマにしており、個人的に好きなAppleの技術の1つです。アプリから独立して開発できる一方、アプリ側とデータを共有する仕組みも持ち合わせているため、サブディスプレイの様にアプリ内のコンテンツを別枠で提供できることが特徴です。

ホーム画面だけでなくロック画面でも表示できる他、同サイズのウィジェットを提供しているアプリが他にあれば、自身で設置していないウィジェットも縦スワイプで切り替え表示できるスマートスタックも兼ね揃えています。

しかし機能として独立性が高い反面、頻繁にユーザーとインタラクションする必要の無いアプリでは必要性が低く、ウィジェット機能を提供していないアプリは多いです。

それでも、ホーム画面などにユーザーの関心が高いコンテンツを切り出し、アプリへ誘導する窓口として優秀な機能だと私は思っています。そして今回どのようなコンテンツがREALITYだと相応しいかを模索してきた解の1つが思い出ウィジェットです。

実装は先の開発合宿で形になっていたものの、育休期間や他の開発作業、ウィジェットの技術的な問題に直面し時間がかかりました。
この記事ではウィジェットの技術的な問題に焦点を当てつつ、実装時に便利だった機能を紹介していければと思っています。

また思い出ウィジェットの機能については以下の記事をご覧頂けると幸いです。

問題1: ウィジェットのメモリ制限

実装完了後にレビューをしてもらっている際、メモリの使い過ぎによる強制終了のエラーが出たと指摘をもらいました。それまで手元の端末での検証時に確認したことが無かったのですが、メモリ使用量の問題なので実行環境によっては問題が頻発する or 発生しづらいという状況となっていました。別の端末を用意し問題を確認できるまで時間がかかりました。

問題の箇所は、ウィジェットギャラリーに表示する画像を抽出する時に起きていました。

問題の行で表示された "high watermark memory limit exceeded"
下の図はその時のDebug Memory Graph

ここで目にする(limit=30MB)ですが、App Extensions Programming GuideのOptimize Efficiency and Performanceという項目に以下のようなことが記されています。

Memory limits for running app extensions are significantly lower than the memory limits imposed on a foreground app. On both platforms, the system may aggressively terminate extensions because users want to return to their main goal in the host app.
Some extensions may have lower memory limits than others: For example, widgets must be especially efficient because users are likely to have several widgets open at the same time.

実行中のアプリの拡張機能のメモリ制限は、フォアグラウンドアプリに課されるメモリ制限よりもかなり低い。 どちらのプラットフォームでも、ユーザーはホストアプリのメインゴールに戻りたいため、システムは拡張機能を積極的に終了する可能性があります。
エクステンションによっては、他のエクステンションよりもメモリ制限が低い場合があります: たとえば、ウィジェットは、ユーザーが同時に複数のウィジェットを開く可能性が高いため、特に効率的でなければなりません。

具体的な数字をドキュメントでは明示していませんが、この項目で説明されている通りウィジェットにおいてはメモリ制限がかなりシビアです。

当初はプレイスホルダー用のアセット画像やアルバムからの写真取得処理といった、画像まわりに原因があるのではと推察していました。

そのためまずアセット画像はTinyPNGを利用する形にしました。
写真取得処理も画像のオリジナルサイズを取得しようとPHAsset.requestContentEditingInput(with:completionHandler) を使用していたのを、PHImageManager.requestImage(for:targetSize:contentMode:options:resultHandler:) でリサイズ済みの画像を読み込んでメモリ消費を抑え、必要最低限のサイズでのロードを試みましたが、それでもメモリ制限のエラーは解消されませんでした。

改めて上記のDebug Memory Graphを見ると、画像取得処理の前からメモリ上限に近い使用量となっていることがわかります。これがメモリが逼迫しやすい状態になっていた犯人で、画像取得処理前に行われる独自に作成したフレームワーク(Appleが提供していないフレームワーク)のロード処理によるものでした。

REALITY iOSアプリは3年ほど前からモジュール分割を積極的に取り組んでおり、今回のウィジェット用フレームワークでも参照が必要な以下の処理は別フレームワークで提供されています。

  • デザインシステム用フレームワーク

  • デザインシステムで定義していないUIコンポーネント用フレームワーク

  • ローカライゼーションや画像などのリソース用フレームワーク

  • extension用フレームワーク

実装に必要な処理を短いコード量で実現できる一方で、今回はこれらのフレームワークに1つでも依存すると、ロードしに行く時点で20MBほど消費し機能実現に大きな障壁となっていました。

そのためウィジェット用のフレームワークに上記のフレームワークで必要な処理を移し、必要なフレームワークはAppleが提供するもの(WidgetKitとSwiftUI)のみに抑えることで、修正前は画像取得直前で20数MBあったのが数MBにまでダイエットすることができました。

同じ轍を踏まないために、XcodeGenのyamlファイル内ではウィジェット用フレームワークのdependenciesの欄に以下のようにコメントを残しています。

dependencies:
  - sdk: WidgetKit.framework
  - sdk: SwiftUI.framework
  # NOTE: 他のF/Wをロードするとそれだけで20MB以上メモリを消費し、メモリ30MB制限によるランタイムエラーが発生する可能性がある
  #       {デザインシステム用モジュール}や{リソース用モジュール}で使いたい処理は、Assets内に直接設定するなど
  #       {ウィジェット用モジュール}内で処理が完結ことを留意する

問題2: Testableにする / デバッグする難易度

先の開発合宿の記事でも取り上げたのですが、ウィジェット用フレームワーク内ではTestableにしたりデバッグすることの難易度がかなり高いです。

LLDBとの相性の問題か原因は定かではありませんが、端末によってブレークポイントが止まったり止まらなかったりします。

またウィジェット用フレームワークは作成時にUnit Test Bundleをincludeできず、ウィジェット用にテストクラスを用意することはそのフレームワーク内ではできません。

Include Tests欄は存在しない

そのためワークアラウンドとして、プレゼンテーション用ロジック自体を別フレームワークに切り出すことで、デバッグやユニットテストはウィジェット用フレーム外で実施できます。しかし今回は問題1で別フレームワークの用意はメモリ制限に影響が出ることが発覚したため、Testableな設計は二転三転した結果断念することになりました。

問題3: systemMediumサイズの設定を問われ続ける

ウィジェットのサイズは全部で3つ(iPad OSのみextraLargeサイズが存在)ありますが必須のサイズはありません(関連ドキュメントに明示されていない)。しかし今回の対応では以下のポップアップが実行処理の都度表示されていました。

ウィジェットのサイズのうちsystemMedium(横長サイズ)がサポートされていないと出るのですが、前述の通りsystenMediumも必須サイズではなくAmazonなど多くのアプリでsystemMediumをサポートしていないウィジェットは存在します。ウィジェットのレイアウト要件についてはHuman Interface Guidelineに具体的にまとまっています。

今回このポップアップは以下の条件で発生しているようでした。

ウィジェット実装のデフォルトサイズがsystemMediumであるためか、おそらくWidgetKit側が内部でsystemMediumの利用を期待しているのだと思われます。しかし今回の条件下でsystemMediumのデザインを定義しない限り実行時に毎回表示されるのは困ってしまうので、WidgetKit側の改修を望みたいところです。

ウィジェットのUIデバッグの改善

技術的な問題が多かった一方で、ウィジェットのUIデバッグはiOS17から使えるようになったPreview(:as:widget:timeline:)などを活用し、Timelineごとの変化をPreviewで確認できるようになりました 👏

このTimelineはウィジェットのUI更新における重要な概念で、端末のパフォーマンスを考慮したViewの更新頻度を定義するためのものです。

https://developer.apple.com/jp/documentation/widgetkit/keeping-a-widget-up-to-date/

Viewの更新頻度が高いと端末のバッテリーに影響が出る為、コンテンツの適切な更新タイミングを見極める必要があります。
そして一通りの更新を実施した後、次回いつリクエストを再度投げるかを定義する更新ポリシーがあります。

またiOS18からは実機の設定にWidget Developer Modeが追加され、設定をOnにするとシステム側のポリシーを削除する形でViewの更新を頻繁に行えるそうなので、より検証がしやすくなると期待しています。

まとめ

この思い出ウィジェットを実装中WWDC24が開催され、その中でAppleの新しいAI技術であるApple Intelligenceが発表されました。

その仕組みは以前よりSiriKitに含まれていたApp Intentsフレームワークを使うというもので、それはウィジェットの編集機能にも用いられています。合わせて発表されたコントロールセンターやロック画面に3rdパーティ製のアプリを配置できる仕組みにもWidgetKitを利用することが紹介されており、数珠繋ぎのように、真新しい技術ではなかったこのウィジェット対応がAppleの最新トレンドにも乗っかることができたというのは副次的効果でした。

今回のウィジェット開発をベースに、今後もまたAppleの提供する新しい仕組みにもチャレンジしていく予定です。