minne事業部モバイルチームのシニアエンジニアをしております、@joshです。久しぶりに日本語の長文を書いています。
minneのiOSアプリを開発しており、直近の半年間は、iOS 12のサポートを終了し、SwiftUI, GraphQL, Combineなど、チームで多くの面白い新技術を活用して開発効率を上げているので、説明したいと思います。
開発体制
まず、技術以前に「人」です。minneは、iOSエンジニア4名で開発しており、デザイナー、ディレクター、ほかのエンジニアと協力して、機能追加と保守を行っています。コミュニケーションは主にSlack, GitHub Enterprise, Google Meetで行っています。ちなみに、当社はテレワークを基本とする勤務体制へ移行したので、完全リモートの環境です。
Project
基本のバージョン管理ツールは定番のGitとGitHub Enterpriseです。また、iOS限定について話すと、去年個人のブログでこの辺りについて説明していますが、以下のツールを使っています。
ツール | 目的 |
---|---|
XcodeGen | pbxprojの生成(神ツールです) |
SwiftLint + SwiftFormat | lint + format |
quicktype | エンティティの自動生成 |
Scaffold | 拙作ですが、ViewModel/Test/ViewModelのテンプレート生成 |
開発言語と主要フレームワーク
95%以上Swiftで、特に古いテストやエンティティはObjective-Cが少しだけあります。新規のものは必ずSwiftで開発していて、残っているObjective-Cは少しずつ減らしています。
使っているSDKのバージョンに関しては、Xcode 12への移行対応が終わっており、近日中に12でビルドしたバイナリーを公開する予定です。
ほぼほぼUIKitですが、いくつかの画面でSwiftUIを使っています。最低対応のSDKはiOS 13なので、LazyStack系やLazyGrid系がまだ使えないのですが、設定周りの画面など、CollectionViewっぽくない画面を今年中にたくさん移行しようと思っています。移行自体は色々考えてプロトタイプを作るなどして、2020年のiOSDCで発表しました。また、UIKitはモダンなAPIを使うように心がけており、例えば、UICollectionViewを使っている新規画面でだいたいDiffableDataSourcesとCompositionalLayoutを使っています。
ログ周りは、Google Analytics for FirebaseやReproなどをPuree-Swiftでラップして、統一したインターフェイスに移行している途中です。
アーキテクチャ
Objective-CがたくさんSwiftと共存していた時代は、RxSwiftなどのFRPライブラリを導入することにチームとして抵抗があったので、MVPのGUIアーキテクチャでしたが、SwiftUIとCombineが発表された瞬間からSwiftUI移行のしやすくさを考慮し、MVVMやReduxなどを検討しました。現在はCombineを使ったMVVMをGUIアーキテクチャに採用しておりますが、画面間のデータフローが実装しやすいThe Composable Architecture (TCA)を最近検討しており、試験的に導入し、評価する予定です。また、モデル層について少しずつ議論して、パターン化しています。
Test
テストをできるだけ書きやすくするために以下のツールを活用しています。
社内ではRuby on Rails経験者が多いというのもあって、RSpecに近い、やさしい書き方ができる QuickとNimbleを使っています。ただ、XCTestを使いたい人は、使って良いというスタンスで、柔軟で平和な日々です。
依存性の注入(DI)はライブラリを利用せず、普通のコンストラクタインジェクションですが、このめっちゃくちゃ便利なProtocol Compositionパターンに助かっています。
Mockは型付けの強いSwiftでは普段作るのが面倒ですが、SourceryのAutoMockableテンプレートを活用して、自動生成しています。
CombineのコードはXCTestExpectationだけでは、以下の問題があるので、CombineExpectationsとCombineSchedulersを使ってテストを書いています。
- debounceなどのオペレーターでschedulerを注入しないと、テストが遅くなってしまう
- 記述量が多い
CI/CD
この辺りは特に力を入れており、ツールとしてFastlane + Dangerを積極的に活用しており、サービスとしてBitrise + TestFlightを使っています。方針として、できるだけ多くのことを自動化することによって、開発とレビューに割ける時間を最大化し、アウトプットの量を増やし品質を向上できると考えています。
Fastlaneはもやはモバイル開発の定番の自動化ツールで、スクリーンショット作成以外は、メインのアクションをすべて活用しています。
- アプリのベータと本番用ビルドを配信
- App Storeメタデータを管理
- プッシュ通知証明書やProvisioning Profileの更新
- CI上のテスト実行
私自身danger/swiftのcontributerですが、恥ずかしいことにまだRuby製のDangerを使っています。Dangerを使って、簡単なところを自動化して、レビューで不具合がないかや保守しやすいかどうかなど、高度なところを我々人間でやっております。(人間いぇ〜い)
- レビュアのアサイン
- デバッグコードが残っていないかの検知
- Storyboardのlint
- タイポの検知(社内唯一の英語ネイティブなので、これで自分からの指摘がめっちゃ減って幸せになった)
- ビルドとテスト結果の投稿
- コードカバレッジの投稿
近い将来SwiftLint実行やラベル管理など、色々GitHub Actionsに移して、CIをより高速化しようと思っています。また、SwiftInfoを導入し、コードベースの変化をより見える形にしようと思っています。
技術課題
技術課題は、ほかのiOSアプリもよくあるものだと思いますが、minneは、Objective-Cで2012年から開発されており、画面が120個以上あるので、ビルド時間が長く、そしてFat ViewControllerがまあまああります。ビルド時間は基本的にモジュール分割で、Fat ViewControllerはリライトで取り組んでいます。(ビルド時間問題は、Apple SiliconのハイスペックなMacBook Proが出たら、「銀の弾丸」であるお金で解決したいです。)
あえて導入していないもの
少し意外かもしれませんが、minneではAlamofireをやめて通信ライブラリや通信のモックライブラリ(OHTTPStubsなど)を一切使っていません。今の時代は、あまり通信ライブラリが必要ないと感じており、また、FoundationのURLSessionを使うのに以下のメリットがあります。もちろん、その反面パラメータエンコードと画像アップロードに使うmultipart/form-data
を自前で実装するのはちょっと面倒でしたが、minneは大量のエンドポイントを扱い、ネットワーク処理がわりとキモなので、通信処理を内製化すると、より精密にそのレイヤーをコントロールできます。また、徐々にGraphQLに移行しているので、その辺りの通信処理を一部apollo-iosに託しています。
- レイヤーが減るので、デバッグもしやすくなる
- アップデート対応がなくなる
- 依存性が減る
個人的にかなり興味を持っているのですが、BuckやBazelなど、xcodebuild以外のビルドシステムは4人体制ではあまり保守できそうにないので、採用していません。
ほぼほぼやめて、あと一歩でなくなるツールやライブラリに関しては、Carthage, RxSwift, JSONの変換ライブラリがあります。Carthageは事前にビルドするので、簡単にキャッシュできるというのが非常に大きいメリットですが、Xcode 12では使いにくく1、Swift Package Manager(SPM)がやっとバイナリーやアセットリソースを使えるようになったので、できるだけSPMに一本化したいです。RxSwiftはあまり多くはないですが、Combineでリプレースしています。JSONの変換ライブラリも標準のCodableでだいたいなくしており、もう少しで消えます。
まとめ
minne iOSアプリでの開発状況です。モダンな環境でSwiftUI、Combine、GraphQLを積極的に増やして、新機能も定期的に追加することにご興味あれば、ぜひ応募してください!また、質問や疑問などあれば、ぜひTwitterで突っ込んでください〜