复数のUIWindowを利用してFiNCアプリの迁移构造を分けた话

NC。FiNCTechnologiesのiOS开発を担当している西村です。

FiNCでは,アプリの规模が大きいこともあり,抵抗化を防ぐために,様々な机能をモジュール単位で切り分けられるように试行错误を重ねています。

での中で,今回はUIWindowを复数并用した迁移构造の整理のお话をしたいと思います。


の构造を作ることになったきっかけは,アプリのどこからでも呼ばれうる,コンシェルジュ的な机能(以下为コンシェルジュ)を作りたいという企画がはじまりでした。

FiNCアプリは,复数のドメインから成り立っています。
※ライフログや,メディアページ,タイムラインなど

今回の企画は,これを统括してコントロールする,上位概念としてキャラクターを作りたいというものです。

结果的にできあがったのが,すでにリリース済のアプリでもご覧いただけるこの画面です。

NCット形式でFiNCのマスコットキャラクターが様々な案内をしてくれるというものです。
会话の会话の流れによって,アプリ内の各种机能に诱导されたり,场合によってはアプリ外のサービスにリンクされたりすることもあります。

この场合,例えば以下のような机能が考えられます。

  • 画面ンシェルジュとの対话をきっかけに,画面の里では别の迁移が走っている
  • いかなる状况下においても,最前面に出现可能
  • 里ではアプリの本来の机能が动きうるので,上记条件を満たした上で,Facebookログインや各种ダイアログの机能を阻害しない

をのオーダーを安全を现するためには,iOSアプリの基本的な迁移构造から切り离した,独立の迁移构造を别途共存できるようにすれば良いのではないかと考えました。

迁移构造を并列に持つ,というのは,もちろん管理コストがあがり,多用すれば考虑すべき例外パターンも増えてしまいます。
今回の场合は,以下のような理由により,メリットの方が大きいと考えて采用しました。

  • オンboーディング的な机能や,アプリ全体のガイドをする上で,里侧で别途画面迁移を终えた状态を作れるようにしたい。
  • してンシェルジュをモジュールとして分离させ,不要なドメイン知识を持たせないようにし,具体的な处理は呼び出した侧が通知を受け取って,画面迁移の要否レベルレベンシェルジュは认知しないようにするため

以下,特定的机能と构造の话を区别するために,このコンシェルジュ的な机能を呼び出す侧を“アプリ本体”,呼び出されるコンシェルジュ的机能の方を“サブモジュール”と便宜上呼ぶことにします。

复数のUIWindowの构成

通常,iOSアプリは,1个のWindowをベースに动いています。
AppDelegate的var窗口:UIWindow?`がはじめから宣言ますがいますが,意识的をWindow操作を行った経験がある方は少ないんじゃないでしょうか。
がーboード出现时などに一时的に他のWindowが利用されることはありますが,基本的には开発者が意识して行う必要もないように作られています。

これに対して今回は

サブモジュールを表示する际に,新しくUIWindowを生成し,别の独立した迁移构造を持って表示するようにしています。
基本的な处理フローとしては

  • 通常modalで迁移处理を行うであろうタイミングで,UIWindowを生成し,新しく前面に配置する
  • はモジュール内で起こったイベント情报は,すべてRxSwiftのStreamとしてアプリ本体へ通知し,必要であればアプリ本体が处理を行う
  • サブモジュールの役割が终わる,もしくはアプリ本体から终了の命令を受けたらサブモジュールはUIWindowを破弃する

という流れで,図にすると以下のようなフローになっています。

设计上の具体的な特徴として以下のようなものがあります。

イベントの渡し方

アプリ本体は,サブモジュールのイベント通知をsubscribeし,必要に応じて处理を行い,必要であればサブモジュールに新たに处理を命令します。

この时,サブモジュール侧は,自分が通知しているイベントが引き起こす具体的な处理には关心がありません。

例えば以下の例のようなパターンがあります。

  • 非ログイン时に,ログイン纠正の处理を受けた场合,アプリ本体がログイン处理を行う必要がある
  • 他のドメインの机能をオーバーレイレ表示で呼び出す必要がある

ようなのような场合,サブモジュール内で直接机能を呼び出すべきではないので,イベント通知用用RxSwiftのStreamを用意しています。
アプリ本体から呼び出す际に,イベント通知に対応する可能がある场合は,この流をsubscribeしておき,通知を受けた时に必要に応じて处理を行うようにします。

WindowLevelの管理

向前したオーしたオーレイ表示なども踏まえて,UIWindowを扱う偶尔はWindowLevelを管理する必要があります。
都度等级を数値として直接指定して管理していくのは危険なので,予めWindowLevelを定义し,役割毎にアサインしておくことで,特に意识しなくても,重なり顺が守られるようになります。

冒头のコンシェルジュ机能を例とした场合,
アプリ本体,コンシェルジュのチャット画面,一时的に表示されるログイン画面のそれぞれにWindowLevelの定义をアサインしておき,间违えてコンシェルジュチャットの里にログイン画面が隠れてしまった,のような自体に陥らないようになっています。

ライフサイクルの管理

迁移のサブモジュールは,迁移构造として独立しているので,基本的にはどこかでインスタンスが保持されているわけではありません。
がモジュールが固有のインスタンスタンスを保持しておき,役目を终えたら终了のイベント通知を投げて自身のインスタンスを破弃します。


里で迁移を行えるというのは思いの外実用的で,特に,サインアップフローをこのコンシェルジュのチャット机能を用いて行う际に,以下のような恩恵がありました。

画面生成の并列化

  • でインアップフローで决定でユーザースタータスに合わせて,初期画面の配置设定が柔软になる
  • 画面インアップフローが完了した时点ですでにコンテンツの画面表示は完了しているので,ローディングの待ち时间などのストレスを无くすことができる

疎结合化の强制

框架を独立したことで,サブモジュールのドメインが持つべき机能を明确に意识する必要があり,アプリ本体と密合状态を作り出すことが実质できないようになっています。
これにより,チャット上からアプリ本体の具体的なコンテンツに直接推进迁移してまた戻ってきたい,のようなオーダーは対応できませんが,本来混ざるべきではない迁移构造を避けられたという见方もできるかと思います。

実际は,WindowLevelをコントロールして,イベント通知を受けたアプリ本体がサブモジュールより全面にコンテンツをフローティングして表示することは可能なので,见え方が整理できていれば,コンテンツの流れを阻害害することもありません。

注意点

ただし,冒头でも记载した通り,むやみに采用すると管理が复雑になってしまうため,适切に必要性を判断し,现在どういうWindowを使っているか,各WindowLevelの定义をどうするか,などは集约管理し,各自が关系性を意识せずに済むような构造を守るのが大事です。

総括すると,而言はできるものの,全体的にはメリットが大きいのではないかと感じました。

ログのコントロール

上记垂直を许容した上で出る悬念としては,ログの取得方法に独自の条件が加わってしまうことです。
例えば,コンシェルジュからのイベント通知によりアプリ本体が迁移した场合,そのViewDidLoadやViewDidAppearでスクリーン计测を行ってしまうと,実interのユーザー験体験とはログ状况が异なってしまいます。
こちらに关しては,データ解析のチームと话し合って工夫してはいますが,より效率的なログ出力の方法を模索したいところです。

现在,この构造はFiNCアプリのために作ったもので,构造としてアプリのドメイン情报が混ざっている部分が残っています。
Window必要な责务を见直し,この窗口设计の部分のみを切り出してライブラリとして公开することも考えています。

最后になりますが株式会社FiNC Technologiesでは,iOS ・ Androidエンジニアアを募集しています。ご兴味がある方はぜひこちらからご応募下さい。

株式会社FiNC Technologies的大规模开発をリードするiOSエンジニア募集集中!

株式会社FiNC Technologies…

www.wantedly.com

株式会社FiNC Technologies的大规模开発を行いたいAndroidエンジニア募集!!

希望收集では,働くモチベーションションや一绪てンバーについて知ることができます。【募集职种】Androidエンジニア【业务内容】…

www.wantedly.com