Mirrativ Tech Blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc.)によるブログです

Flipperを使ったiOSアプリの開発体験向上&iOSエンジニア目線でのミラティブ社の魅力について

はじめに

初めまして!Mと申します。 私は、2022年8月中旬より現在まで、iOSチームでインターンとして参加させて頂いています。 (実はこのミラティブのTechブログを読んだことがきっかけで、インターンに参加することになりました) これまでの期間、特に私は、開発体験の向上のためのデバッグ環境改善に関するタスクを行なってきました。

本記事では、MirrativのiOSアプリ開発における、Flipperというデバッグツールの活用事例および実装のポイントについて説明します。 また、私がインターンの中で感じたミラティブ社の魅力や、どんなことが学べたのかについてお伝えしようと思います。

目次

MirrativでのFlipper活用例

Flipperとは

Flipperとは、Meta社(旧Facebook社)が開発したモバイルアプリ開発のためのデバッグツールです。 アプリを起動してデバイスをPCに接続すると、Flipperのデスクトップアプリを通して、様々な情報を取得したり、ログを閲覧したり、操作を行ったりと、幅広くデバッグを支援してくれる便利なツールです。

https://github.com/facebook/flipper

Flipperプラグイン

Flipperには、元々便利な機能が搭載されていますが、自分でプラグインを作成することで機能を拡張することが可能です。プラグインの作成は、PC(デスクトップ)側とクライアント側(iOS、Android)でそれぞれ行う必要があります。 クライアント側では、OSごとにそれぞれプラグインの実装が必要となっており、それぞれ、Kotolin、SwiftまたはObjective-Cで実装を行います。 一方で、デスクトップアプリはElectron+Reactで作成されているため、 PCのOSによらず共通の実装(JavaScriptやTypeScriptを使用)ができます。 また、クライアント側のOSにも依存せず、iOS/Androidで使い回すことが可能となっています。

プラグインの作成方法は過去記事で紹介しているので以下の記事をご覧ください。
(過去記事)【Android】FlipperのCustomPluginを作成してデバッグ効率を改善する

ミラティブのデバッグ体験の改善点

iOSアプリ内のデータの流れ

MirrativのiOSアプリ内の主なデータの流れを下図に示します(配信中の映像や音声のやり取りなど一部省略しています)。 サーバとの主な通信はメインアプリを通じて行われています。 Unityフレームワークからは、一部実験的に直接API叩いているものを除いて基本的には直接サーバとの通信は行われず、メインアプリを通じて行われるようになっています。 また、配信関連の通信は、Broadcast Extensionからメインアプリを介さずに直接行われています。 しかし、コメントなどの情報は、メインアプリ上に表示を行う必要があるためBroadcast Extensionからメインアプリ側に送信されるようになっています。
(過去記事) Mirrativ×Unity as a Library 活用事例と開発テクニック

デバッグ面で大変だったこととその解決策① 配信編

配信中は、メインアプリとBroadcast Extensionの間で相互に様々な情報やイベントが送受信されています。 これらは別プロセスなので、Xcodeからは一方にしかLLDBをアタッチすることが出来ません。 よって、きちんとデータが送受信できているか、また、うまくデコード出来ているかの確認がやや大変でした。

また、視聴者がギフトを贈った際に配信者に通知する機能などは頻繁に、新たな実装が追加されます。 クライアント側では主に、WebSocketベースのPubSubライブラリから受信するメッセージのハンドリングを実装することになります。 その際、デバッグを行うために、メッセージの受信を再現するために毎回UIを操作することは大変です。 よって、一度受け取ったメッセージを簡単にリプレイして受信の再現を行うことのできる仕組みがあると便利そうです。

デバッグ面で大変だったこととその解決策② ログ編

Mirrativのクライアントアプリでは、ログの管理にPureeというライブラリを使用しています。
cookpad/Puree-Swift

デバッガがアタッチされていれば、ログはデバッグコンソールに表示されます。 しかしログ確認のためだけに、毎回Xcodeからアタッチするのも少々面倒であるので、PCに接続するだけでログを取得できる仕組みもあると便利そうです。

デバッグ面で大変だったこととその解決策③ Unity編

Mirrativアプリでは、Unity as a Libraryによって3Dアバターやライブゲームの動作を行っています。 メインアプリとUnityライブラリの間の動作の連携のために、相互に多くの通知や情報を送受信しています。 送受信時の動作のデバッグを行う際に、イベントをリプレイしたい状況は多々生じます。 その度に毎回UIを操作して再現を行うのが大変でした。

また、Unityに関連して、実際にアプリを操作した時のメッセージのやり取りの確認を行う機会も多いため、デバッグログとは独立した表示が用意しようと考えました。

Android アプリで開発されたプラグイン

これらの問題点を解消すべく、Androidアプリでは以下のようなプラグインが開発されました。  各プラグインの要件は以下の通りです。

1. Broadcast Server Plugin

  • バックエンドから受け取った、コメント情報および配信関連のイベントを表示する
  • アプリがバックエンドに対して送信した、コメント情報および配信関連のイベントを表示する
  • ソートやフィルタ、検索などの機能を提供
  • Flipperデスクトップから、コメント情報および配信関連のイベントを送信して、バックエンドからの受信を再現する

2. Puree Plugin

  • Pureeを通じて記録されたログをFlipperデスクトップに表示する
  • ソートやフィルタ、検索などの機能を提供

3. Unity Plugin

  • Unityフレームワークからメインアプリに送信されたイベントをFlipperデスクトップに表示
  • メインアプリからUnityフレームワークに送信されたイベントをFlipperデスクトップに表示
  • ソートやフィルタ、検索などの機能を提供
  • Flipperデスクトップからイベントを、Unityフレームワークに対して送信して、メインアプリからの受信を再現
  • Flipperデスクトップからイベントを、メインアプリに対して送信して、Unityフレームワークからの受信を再現

これまで、Androidアプリにのみ対応が行われていました。 しかし、先述の通り、PC(デスクトップ)側のプラグインはiOSにおいても使い回すことが可能であるので、iOSアプリでの対応は、クライアントプラグインの作成のみで済みました。

iOS用プラグインの実装で工夫した点

App ExtensionからのFlipperの利用

Flipperの設定は、アプリ起動時にメインアプリ側で行います。App Extension側で設定を行おうとしても、内部で使用されているUIApplicationsharedが使用できないため、エラーになってしまいます。

今回は、メインアプリとApp Extensionの両方からFlipperデスクトップとの間で送受信を行いたいです。 よって、Flipper関連の設定は全てメインアプリ側で行いつつ、App Extenion側からの利用はメインアプリを中継して行う実装を考えました。

Target間のデータ共有

メインアプリとApp Extensionはそれぞれ別のプロセスで動いています。 iOSでは、共有コンテナにアクセスしプロセス間通信(IPC)を使用して通信するための仕組みとして、App Groupと呼ばれるものが用意されています。

UserDefaultsでApp Goupを使用してデータ共有を行うには、suiteNameにApp Group Identifierを設定することで行えます。

UserDefaults.standard
/* の代わりに */
UserDefaults(suiteName: "<App Group Identifier>")

また、ファイルの共有は、App Group専用の領域に書き込むことで、それぞれのプロセスが同じファイルにアクセスできるようになります。

// App Group専用の領域
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "<App Group Identifier>")

本要件での課題

今回、プラグインをiOSで実装を行うにおいても、ログやイベント情報のやり取りのために、プロセス間でのデータ共有が必要となっています。 本要件では、やり取りするデータ量が比較的小さいことや扱いの手軽さから、UserDefaultsを使用することとしました。

UserDefaultsの変更をKVOで監視して、変更があった場合に所定の処理を行う実装にすれば良さそうだとも思います。 しかしApp Groupを使用している場合、KVOによる監視は上手く動かないようです。
(参考) App Groups で共有したファイルを監視してプロセス間の変更を Combine で検知する

UserDefaultsは以下のパスにplist形式で保存されているので、ファイルの変更検知を行うことも考えられると思います。

<Home Directory>/Library/Preferences/<Bundle Identifier>.plist
# <Home Directory>は`NSHomeDirectory()`などで取得できる

しかし、UserDefaultsは、これまた別のプロセスであるcfprefsdによって管理されており、ファイルへの書き込みは常にUserDefaultsでsetしたタイミングで行われるわけではありません。 よって、この方法も使用できません。

Target間の通知

そこで、通知を利用してUserDefaultsの変更を伝えるという方法を考えました。

MacOSでは、プロセス間で通知を送るための仕組みとして、DistributedNotificationCenterというものが用意されていますが、iOSでは使えないようになっています。

よって、代わりにCFNotificationCenterを使用します。CFNotificationCenterは、NotificationCeterの上位実装に当たるCoreFoundationの関数です。

CFNotificationには、DarwinNotificationCenterというものが用意されており、これを利用することでプロセス間で通知を送受信することが出来ます。

最終的な実装

以上より、最終的にプロセスを跨ぐログやイベント情報の伝達は以下のような流れで行うよう実装しました。

  1. 受信側: 通知を購読
  2. 送信側: 伝達したいデータをUserDefaultsに書き込む
  3. 送信側: 変更通知を送る
  4. 受信側: 変更通知を受け取る
  5. 受信側: UserDefaultsを読みにいく
  6. 受信側: 任意の処理

できたこと/よかったこと

以上より、iOSアプリにおいてもAndroidアプリと同じく、Flipperによるデバッグ機能の対応を行うことが出来ました。 AndroidとiOSではそれぞれ制約を受ける部分が異なり、クライアント側のプラグインの実装はそれぞれかなり異なっています。

今回対応を行ったことで、新規機能実装の際はもちろん、バグの調査やデグレの有無の調査など、デバッグ時の効率化を図ることが出来ました。 これまでは、ローカルで簡易的なサーバを立てるなどして確認を行なっていたので、それらの作業が必要なくなるのは嬉しいです。

Flipperに元から搭載されているプラグイン

Flipperには、自分で作成したプラグイン以外にも、元から付属している便利なプラグインも使用可能です。 Mirrativアプリでも、先ほど紹介したプラグインに加えてこれらも使用しています。 それぞれ簡単に紹介させていただこうと思います。

UserDefaults Plugin

UserDefaultsの中身を覗いたり、書き換えたりすることのできるプラグインです。
変更履歴を追うことも可能となっており、デバッグ時に大変役に立っています。 また、standardだけでなく他のsuiteの表示にも対応しているので便利です。

NetWork Plugin

アプリ内のネットワークトラフィックのログを表示することができます。

Layout Plugin

アプリ画面のviewの階層をデバッグすることが可能です。
viewを選択すると、frameやbackgroundColorなどのプロパティが一覧で表示されます。 さらに、これらプロパティは、編集することも可能であり、リアルタイムでアプリ画面に反映されます。

選択中のviewは、実際のアプリ画面上でもハイライトされており、どのviewを指しているのかが分かりやすくなっています。

A B C

Crash Reporter

端末内に保存されているクラッシュ情報をFlipperから覗くことも可能です。
ただし、symbolicateされていない状態のままの表示です。 (dSYMファイル選択してsymbolicatecrashを実行してくれるUIあってもいいなと思ったりしてます。)

Console Log

デバイスログを表示することが可能です。 こちらは、アプリ側にpluginを導入する必要がなく、接続するだけで使用可能です。

まとめ

今回は、MirrativアプリにおけるFlipperの活用事例と、iOSでのプラグイン作成において工夫した点について説明させて頂きました。 アプリ開発において、やはりデバッグには多くの時間がかかってしまうものなので、できるだけ効率化を図りたいと思っています。 これからもより良いデバッグ環境を求めて、日々改善していこうと思います。

ミラティブ社の魅力

私は、これまで10ヶ月近くインターン生として、ミラティブ社で働かさせていただいてきました。 これまで、私が業務に関わらさせていただく中で感じたミラティブ社の魅力について、今回はiOSアプリエンジニアとしての視点に絞ってお伝えしようと思います。

iOSアプリ開発はこんなプロダクトが面白い!

一口にモバイルアプリと言っても様々なプロダクトがあり、そのサービスの内容によって開発面でも大きな違いがあります。 使うフレームワーク、開発規模、必要な技術範囲、UI関連かロジック関連のどちらの開発が多いか、など挙げられると思います。 また、最近ではFlutterなどのマルチプラットフォーム開発も流行っていますが、せっかくiOSエンジニアをやるならその強みを活かしたプロダクトの開発を行いたいと考えます。

私は複数の会社で、iOSアプリの開発を行なってきましたが、いくつかのプロダクトと関わる中で感じた、iOSエンジニアへおすすめのサービス内容を紹介します。

1. 物理デバイスと連携するアプリ

IoTやヘルスケアに関するアプリが例として挙げられると思います。 このようなアプリは、BluetoothやWi-Fiを用いて、物理デバイスとの通信を行う必要があり、これらのフレームワークについての知識を得ることができます。 OS依存の制限や機能もありパフォーマンス面で見ても、iOSネイティブの知識を活かせる分野だと思います。 また、多くは画面の中だけで完結してしまうことの多いアプリですが、物理デバイスを作動させ実空間との連携ができるという体験は、大変貴重なものだと思います。 普段の開発においても、パソコンに向かうだけではなく、物理デバイスの動作確認を行なったり、Firmwareの仕様や実装を確認したりと、通常のアプリ開発では無いようなタスクもあり、一生飽きることのない業務内容です。

また、iPhoneに搭載されている各種センサを扱うアプリも同様だと思います。 OSごとに搭載されているセンサの種類も違えば、性能も違うでしょうし、OSにより掛けられている制約も異なります。 これらの差異は、マルチプラットフォーム開発においてライブラリ側のラップである程度吸収はできるとは思いますが、それぞれのプラットフォームの機能を最大活かした開発を行うには、やはりネイティブの知識が必要ではないかと思っています。

2. 配信系のアプリ!!!!

まさにMirrativアプリのことです!!!

フロントエンドエンジニアの方の中には、自身の業務内容を「JSON色付け係」であると自虐している様子を見かけることもあります。 私も、ときたま同様に感じてしまうことのある一人です。 確かに、世の中の多くのアプリの役割は、言ってしまえば「サーバから受け取った情報を表示する」ようなものも多いような印象があります。 しかし、配信系アプリは、iOS端末側が主に情報(音声や映像)を提供する側にもなり得るわけです。 プロダクトとしてもとても重要な役割を担っており、とてもやりがいを感じられます。

また、音声や映像に関するフレームワークであったり、画面共有に関するReplayKit、さらに数値計算や信号処理のためのAccelerateなど、触れることのできるフレームワークも多岐に渡りたくさんの知識が身に付きます。業務で学んだことは、個人開発にも活かせるので開発の幅が広がり、より楽しくなると思います。実際、自分も業務を始めてから、たくさんのアプリやライブラリを作成することができました。

仕様に沿って実装を行うだけでなく、課題を見つけて挑戦できる!

新規機能の実装やバグ修正などのタスクももちろんありますが、それ以外にも開発面やデバッグにおける課題を見つけて、それに挑戦することができます。

例えば、MirrativアプリではデバッグやQAのために複数のサーバが用意されています。 各種イベントや新機能をのテストを分離して行うためです。 そのため、デバッグ版のモバイルアプリでは、あらかじめ接続先サーバを選択して使用開始するようになっています。 しかし、接続サーバを切り替えると、セッションが破棄されてしまい再度同じサーバに接続してもログインし直さないといけないという問題がありました。 また、Mirrativアプリではアバター素材や画像のキャッシュなどがローカルに保存されているのですが、それらも有効に使われなくなってしまいます。

そこで、iOSアプリ側で、各サーバごとにセッションを保持しつつかつストレージもサーバごとに別に用意する、いわば1つのアプリ内に接続先サーバごとの個別の環境を用意する機能を実装することを考えました。 初めからその実装方法や実現可能性に検討がついていた訳ではありませんでしたが、調査を進めながら実装に挑戦することができました。 そこで得られた技術や知識をチームのメンバーに共有する機会もあり、大変勉強になりました。

毎週の勉強会がある!

iOSチームでは、毎週水曜日にオンライン上で勉強会を行なっています。 各回、担当者が、現在行っているタスクや学んだ内容からテーマを決め発表します。 その後、フランクに皆で意見を出し合って理解を深めることができます。 自分が今まで触れてこなかったような内容に触れられる良い機会となっています。

例えば、かなり前の話にはなりますが、SwiftUI導入にあたっての考慮すべきことや課題について話し合う勉強会が行われました。 ほぼすべてをUIKitを用いて開発されてきていたアプリをSwiftUIに置き換えていくことは、 新規アプリをSwiftUIで開発し始めることと、大きく難易度も課題も異なっています。 そのノウハウを経験あるiOSエンジニアの方々の意見を聞きながら学べたことは、とても勉強になりました。 個人開発でも、UIKit製のアプリがあるので学んだ内容を活かしてSwiftUIへの移行を進めていこうと思いました。

他には、過去のiOSDCの動画を皆で視聴し、話し合う機会もありました。 個人でも視聴していましたが、それぞれ経験も着目ポイントも異なるので、話し合いによってより理解を深めることができています。

勉強会はiOSチームだけでなくUnityやAndroidチームなどでも行われており、時間がずれているため自分が所属するチーム以外の勉強会にも参加することができます。 自分自身、Androidアプリの開発にも興味を持っているので気になっています。

さいごに

今回は、iOSのMirrativアプリにおけるFlipperの活用事例、およびiOSエンジニアにとってMirrativの楽しいところについて紹介しました。 ここまで読んでいただきありがとうございました。 また、これまで私をサポートいただいたマネージャやiOSチームのみなさんに感謝致します。 まだインターン生として所属しておりますのでよろしくお願いします。

ミラティブのインターンは楽しいのでおすすめです。

We are hiring!

ミラティブでは新卒およびインターンを募集しています!

興味を持った方は、是非エントリーお待ちしています。

www.mirrativ.co.jp

mirrativ.notion.site