CA.swiftは、サイバーエージェントのABEMAやAmeba、AWA、tappleなどを担当しているiOSエンジニアによるiOSエンジニアのための勉強会です。 それぞれのチームで使用している技術や開発体制など、開発の中でのノウハウを惜しみなく発信していきます。 

第22回のテーマは 「Swiftの進化を活かした技術基盤への挑戦」 です。サイバーエージェントでは、Swift ConcurrencyやSwiftUI、TCAなど次世代技術を活用しながら、いかにして現場の技術基盤を進化させてきたのか。そのプロセスで生まれた試行錯誤や、技術選定のポイント、実例に基づく工夫の数々を共有する予定です。

本記事は、2025年01月08(水)に開催した「CA.swift #22」において発表された「SwiftUI移行のためのインプレッショントラッキング基盤の構築」に対して、社内の生成AI議事録ツール「コエログ」を活用して書き起こし、登壇者本人が監修役として加筆修正しました。


廣川 昂紀 | 株式会社AbemaTV

GitHub : KokiHirokawa X : @pihero13


2020年4月に新卒で入社し、以来ABEMAのiOSアプリ開発に従事しています。「SwiftUI移行のためのインプレッショントラッキング基盤の構築」というタイトルで発表させていただきます。

先ほど安井さんの発表であったように、ABEMAの新規プロジェクトでは技術刷新も目的の1つとしており、UIの実装ではSwiftUIを積極的に利用しています。

現状では、大半がUIKitで実装され部分的にSwiftUIを取り入れていますが、徐々にSwiftUIに移行しスポットでUIKitを利用する形にしていきたいと考えています。

移行における課題としては、キャッチアップコストや、iOS 15.0のサポートなどが挙げられます。

今回の発表に関しては、上記に加えてインプレッショントラッキングの仕組みをサポートするという点があったため、ここを切り取って解説していきます。

目次です。

発表は廣川昂紀が担当します。2020年に株式会社AbemaTVに新卒入社し、5年間ABEMAのiOSアプリ開発に携わっています。

では本題に入り、インプレッショントラッキングについてお話しします。まず前提として、行動ログについて軽く触れます。

ユーザーの行動を分析してビジネス戦略上の意思決定を行うため、アプリ上では行動ログを取得しています。

例えば、どの画面をユーザーが見てくれたのか、何をどこまで視聴したのか、どのボタンを押したかといった行動の軌跡をログとして送信し分析しています。A/B テストでも、これらを活用し最終的な判断をしています。

それらの行動ログの1つとして、画面上に表示されるどの要素がユーザーに見られたかをインプレッションログとして収集しています。

例えばこれをシンプルに実装しようとすると、以下のようにonAppearで判定する方法が思いつきます。

しかし、「見た」の定義はもう少し複雑です。サービスによって、その定義は異なってくると思います。

例えば、その要素の80%以上の領域が画面内に2秒以上表示されたら、それはユーザーが「見た」とする、という仕様を定義できます。

その場合、仕様に準拠した実装をする必要があります。UIKitではこれらを満たすための仕組みがチーム内で準備されていたのですが、SwiftUIへ移行する際には再度基盤実装が必要になるというところで基盤の構築を進めていきました。

前提です。ABEMAでは、多くの画面がマイリストのようにスクロール可能になっています。

UIKitの基盤実装では、UITableViewやUICollectionViewで表示するコンテンツのインプレッションログを送れるようにしていたため、SwiftUIでもまずはリスト表示されたコンテンツのインプレッションログを送れるように設計しました。

大きく3つの要素があり、1つ目が左側のTrackableScrollViewです。

トラッキング可能なScrollViewを用意しています。トラッキング対象のビューは、TrackableViewにあたります。あとはロジックを含むImpressionTrackingControllerを用意しています。利用イメージはこのような形です。

StateObjectとしてViewにコントローラーを保持しています。configurationでは仕様によって値を調整できる形にしています(先ほどの例だと80%以上の領域が2秒間表示されたこと)

表示部分に関してはTrackableScrollViewを用意し、トラッキング対象のView(ここではFruitsView)をリスト表示するようにしています。FruitsViewにTrackableModifierを付与し、トラッキングされた際に実行する処理をクロージャで指定できるようにしています。

下の部分でenvironmentにコントローラーを渡しているのは、TrackableScrollViewやTrackableModifierの中でImpressionTrackingControllerを使うことがあるので、渡しやすくするためです。

表示領域の何パーセントが表示されているかという計算を行う必要があるため、TrackableScrollViewではframeをImpressionTrackingControllerにキャッシュするようにしています。

また、リスト表示の場合、下スクロールすると上の要素が隠れていき、下の要素が表示されてくるような動きになります。そのため、スクロールしたタイミングで判定できるようにスクロールイベントを伝達しています。

iOS 15.0をサポートする必要があるため、GeometryReaderを活用しています。

今度はTrackableViewについてご紹介します。こちらはトラッキング対象のビューに関するものです。コントローラーからスクロールイベントが届いたら、visibilityの計算を行います。その際、先ほど説明したルートビューと実際のトラッキング対象ビューの表示領域を使ってvisibilityを計算します。表示領域が80%以上になったら、1秒や2秒といったタイマーを設定します。逆に、ビューが隠れた場合はタイマーを停止するといった、比較的シンプルな実装になっています。

最後に、トラッキングされた際の処理です。didTrackのイベントが来たら、ここでログを送信するという形になります。

実際には、このログ送信の部分はクロージャでにTrackableModifierに渡すような形になるため、トラッキングされた際の処理は自由に決めることができます。

ここからは、工夫した点についてお話ししようと思います。

一つ目はデバッグ画面に参考実装を兼ねたデモを作りました。

画面が二つあるのですが、左側がシンプルなリストビューを作成しています。右側は、ABEMAのホーム画面などでよく見られるもので、縦にも横にもスクロールできる画面です。

トラッキングされた箇所には、緑色のチェックマークが表示される形になっています。

デバッグ画面を作って動作確認しやすくなったのは良かったのですが、最初はシミュレーターで動作確認を進めていました。ただ、実機で確認したところ、シミュレーターと実機で挙動が変わる部分があったため、今後も実装を変更した時は両方で確認しておきたいと思いました。

また、最終的に新規画面の実装に適用してリリースする場合には、OSのメジャーバージョンも網羅し挙動を確認するようにしました。

2つ目はUIテストです。画面回転した時に見えている部分だけチェックマークがついて、もう一度画面回転して見える範囲が広がったら、また新たにチェックボックスがつく動作をテストしています。また、アプリがバックグラウンドに移行したときには要素が見えていないので、フォアグラウンドに戻ってきた時点ではチェックマークがついておらず、戻ってから数秒経過してから見えるようになる、といった挙動もUIテストで担保するようにしています。

今までUIテストを書く機会は多くなかったのですが、今回書いてみて想像以上に実行時間が長いと感じました。UIテスト自体は、デメリットとして実行時間が長いというのはよく言われる話だと思いますが、実際に動かしてみると自分が端末を操作するよりも一つ一つの挙動が遅かったり、このあとお話しするような工夫が必要になることで、更に時間が掛かってしまいました。

表示内容の検証にも時間を要するため、例えば検証している間にインプレッションが発生してしまうことがあります。最初は1秒に設定していたのですが、検証自体に1秒以上掛かるケースがありました。

例えば、まず画面を表示します。一番最初に、緑色のチェックマークが表示されていないことを確認します。1秒経ったら、今度はチェックマークが表示されていることを確認したいのですが、表示されていないことを確認している間に、1秒経過してしまうのです。

そのため1秒から最終的には3秒に変更し、より多くの時間が必要になりました。

ボタンがタップできずにテストが失敗するケースもありました。ここは、タップ可能になるまで待機するなどの対応が必要でした。

固定量のスクロールができないのも難しい点でした。今回は表示領域の%もテストしたいので、例えば20pt分だけ下にスクロールし隠れていたコンテンツが表示されたかを確認したいケースがありました。実現できそうなAPIはあるのですが、慣性スクロールが働いてしまい、厳密な固定量のスクロールが難しいという問題がありました。

そのため、デバッグ画面では、一番下までスクロールするためのボタンを追加したり、セルの高さを例えば7.5個表示されるようなサイズに固定値で配置しました。最後のセルは50%の領域しか見えていないので、70%以上の表示領域という条件にしたら見えていないはずだ、というようなテストを書いたりしました。

実際の機能画面でこのようなテストを行うのは難しそうだと感じたので、今はこのデバッグ画面だけテストを実行しています。

また、Environment Variablesを活用し、トラッキングされた場合にトラッキング対象にオーバーレイを載せるようなデバッグ機能も用意しています。トラッキングされていない場合はオーバーレイが赤色に、トラッキングされたら緑になり、視覚的にデバッグできるようにしています。

まとめです。

SwiftUI主体の実装に移行するにあたって、ABEMAではインプレッションログ送信するための仕組みが必要になりました。TrackableScrollViewやTrackable Modifierを用意することで、実装コストを抑えてインプレッションログの送信ができるような基盤実装を用意しました。

途中UIテストを書いてみて、メリットは大きいと感じる反面、実装や運用コストは少し高めなところがあったという点が学びとしてありました。

発表は以上です。ご清聴ありがとうございました。