ココナラiOSアプリ大改築計画

みなさんこんにちは、ココナラでiOSアプリの開発を担当する小林です。

ココナラiOSアプリはまもなく、大幅リニューアルから2年が経とうとしています。 その間に行われたサービス強化や変更への対応により、 アーキテクチャの不備やビルド時間の増加といった問題 が徐々に現れはじめています。

このため今春より、以下の取り組みを行っております。

  • アーキテクチャの刷新
  • ビルド時間の改善

今回は、これらの取り組みに関する概要とその成果についてご紹介したいと思います。

一気にリニューアル!といきたいところだが...

同じコードを2年もメンテナンスしていると、粗が目立ってきます。その理由としては

  • 改修を重ねることで、コード品質が低下した
  • 言語や開発ツールのアップデートにより、よりスマートな実現方法が提供された
  • 自分が成長して、より良いコードが書けるようになった

など、様々な要因があるかと思います。 こうなると一気にリニューアルしたいという気持ちになるものですが、そこは一度冷静に考え、徐々に変えていくことにしました。段階的な移行は 最終形態も重要ですが、その進め方を練ることがより重要 になってきます。

段階的なアーキテクチャのリニューアル

ココナラiOSアプリでは Clean Architecture を採用しています。 しかし当初 Clean Architecture への理解やアプリへの最適化が不十分だったため、徐々に設計上の問題が現れるようになりました。そこで得た反省点を生かし、よりアプリに最適なアーキテクチャを考案しました。

f:id:coconalainc:20180827171453p:plain

Clean Architectureの設計思想を踏襲しつつも、アプリに不適と考えたものは簡素化したり、RxSwiftの導入方法を見直して結果の監視をしやすくするなどの工夫を行いました。再設計時に感じたことは、 そのアーキテクチャに厳密であることより、様々な要件を十分満たすものであるかが、より重要である ということです。アーキテクチャの詳細についてはまた別の機会に紹介できたらと思いますが、以前と比較して以下の課題を解決したものになっています。

  • View
    • ステートを減らし、なるべくPresenterの状態を参照
    • プロトコル志向によりDRYに
  • Presenter
    • ステートマシンによる、大局的な画面の状態管理を一元化
    • プロトコル志向によりDRYに
    • DI可能
  • UseCase
    • ステートレス化
    • ビジネス単位にロジックを集約
    • DI可能
  • Repository
    • Observable, Single, Completable の使い分け
    • プロトコルを規定し、スタブの導入を容易に
  • Model
    • Entity, Translatorと統合し、アプリに適した一気通貫のモデルに
    • extension でビュー寄りの属性を拡張

現在は改修の入った機能から、徐々にこの新アーキテクチャへの移行を進めています。

ビルド時間改善のためのプロジェクト分割

プロジェクトが大きくなるにつれ、Swiftコードのビルド時間が問題になってきました。 Xcode8 から Whole Module Optimization を利用した時間短縮テクニックを導入したものの、 一回のビルドに2分近く 待たされるのは、かなりの時間ロスになります。

プロジェクトを複数に分割すると、Swiftコンパイラからモジュール単位での依存関係が明確になり、不要なビルドを減らせることが期待できます。

Xcode10 (Beta) では Incremental ビルドが賢くなり、ビルド時間もかなり改善されたようです!

プロジェクトの分割単位

ココナラiOSプロジェクトは、以下の単位で分割・依存関係を持っています。

  • App: ViewControllerとPresenter、セルなどの画面固有UIコンポーネント
    • CustomView: 汎用のカスタムUIコンポーネント
    • Core: ビジネスロジックを提供するUseCaseとRepository
      • API: モデル(DTO)と、APIへのリクエスト/レスポンスを行うロジック

こちらも機能追加や改修が生じたタイミングで、段階的な移行を進めています。

分割に際しての懸念事項

特に大きなプロジェクトの分割に着手すると、以下が問題になってきます。

publicアクセスの付与とMemberwise Initializer

分割を行わずひとつのプロジェクトで開発を進めていると、privateinternal (または省略)を使い分けるだけで良かったのですが、プロジェクトを分割すると型やメソッドを上位モジュールから参照できるよう、 publicopen を適宜付与していく必要があります。 (これがまた地味な置換作業になる場合が多い...)

また、型に public アクセスを付与すると、それまで暗黙に定義されていた Memberwise Initializer が利用できなくなり、イニシャライザを実装しなければなりません。

下位モジュールの import が必要

下位モジュールで定義されている型や関数を利用したい場合、通常は明示的に import しなければなりません。ただこれに関しては、以下の方法で暗黙インポートすることが可能です。

  • フレームワークから下位フレームワークを暗黙 import するには

フレームワークのターゲット作成時に自動生成されるヘッダファイルに、暗黙インポートしたいモジュールを追記します。

...中略...

@import CoconalaAPI;    // Objective-C構文のため @import を使う
  • アプリケーションから下位フレームワークを暗黙 import するには

Bridging Headerを追加し、暗黙インポートしたいモジュールを追記します。

...中略...

@import CoconalaCustomViews;
@import CoconalaCore;
@import CoconalaAPI;

Info.plistUserDefaultsBundle などの振る舞い

下位のフレームワークから Info.plistUserDefaults を参照したとき、どのような振る舞いになるかを理解しておくべきでしょう。

  • 下位フレームワークから Bundle.main を呼び出すと、アプリケーションのバンドルが返る
  • Bundle.main.infoDictionary には、アプリケーションの Info.plist の内容が入っている
  • UserDefaults.standard は通常、アプリケーションのどこからアクセスしても共通のストアを参照する
  • UIStoryboardUINib をインスタンス化する際、bundlenil を渡すと Bundle.main を参照するので注意
    • フレームワークのバンドルを参照するには、 Bundle(for: AnyClass) でそこに実装されているクラスを渡す方法がある

現在の状況

8月末現在の進捗状況は以下のとおりです。

新アーキテクチャ

  • 全体のおよそ10%程度のコードが新アーキテクチャに移行
  • 新アーキテクチャによる事例(パターン)が揃ってきたことで、今後の改修はこれを参考とすることで加速する見込み

プロジェクト分割

  • 全体のおよそ15%程度のコードがプロジェクト分割された
  • 複雑な依存関係により難航した実装の多くが分割を終え、今後は比較的スムーズに進行しそう
  • ビルド時間の改善は限定的なものに留まっている (下記参照)
アプリバージョン v3.6.0 v3.12.3
ステップ数(※1) 133,705 147,327
フルビルド 114s 143s
Appモジュールの変更ビルド 63s 68s
Coreモジュールの変更ビルド - 68s
APIモジュールの変更ビルド - 9s

ビルド時間は以下の環境で計測しています。

  • MacBook Pro (Retina, 15-inch, Late 2013) Intel Core i7 2GHz(4-Core)
  • Xcode 9.4.1
  • Whole Module Optimizationを有効

※1サードパーティ製ライブラリを含む、Swift/Objective-Cのコードステップ数

まとめ

移行はまだ道半ばではありますが、今のところ良い結果が出ているのではないのでしょうか。引き続き移行を進めながら、アーキテクチャとビルドの改善を実現していきたいと思います。

そしてこの先もサービス内容の変化やプロダクトの成長、より洗練された開発手法の登場により、アーキテクチャの見直しが必要となる日が来ることでしょう。その時点における最新・最先端の技術をただ闇雲に取り入れるのではなく、現状と対比しながらどのようにステップアップしていくか?その 事前検証と準備を万端にすることが、この先も変わることのないベスト・プラクティスだ と私は考えています!

一緒にアプリをつくってくれる仲間も募集中です!

www.wantedly.com

www.wantedly.com



お知らせメール登録
よもやまブログの更新時にメールでお知らせします。