smash. iOSアプリで採用しているアーキテクチャと改善について
こんにちは。SHOWROOM事業本部開発部メディア開発グループの菊池です。
今回はsmash. のiOSアプリで採用しているアーキテクチャと改善してきたことについて紹介させていただきます。
smash.とはスマートフォンでの視聴に特化した縦型&短尺映像を配信するバーティカルシアターアプリです。
アーキテクチャについて
はじめに
smash. iOSで採用しているアーキテクチャはCleanArchitecture + MVVM を採用しています。
こちらのRepositoryを参考に作成しています。
設計
基本的には以下のような設計で1年半近くは実装していました。
CleanArchitecture + MVVMによるフローを踏襲
UIはSnapKitをベースに作成
RxSwiftに依存
画面遷移はCoordinatorパターンを採用
1年半近く運用してみて
Pros
アーキテクチャが定まっているので、レビューがしやすい。一定の品質を担保ができる
画面遷移もCoordinatorパターンに準拠しているので、Viewから直接遷移することがないので、複雑化しない
SnapKitを利用しているので、似たようなViewを作成する場合は再利用して実装をしやすい
Cons
新規の画面追加仕様、変更が多くあると工数が膨れ上がる
SnapKitで実装してる反面レイアウトが複雑化すると、buildするまでデザインが確認しづらい
課題解決 : 1
>新規の画面追加仕様、変更が多くあると工数が膨れ上がる
施策が一通り実装し終わったタイミングで、チームとして振り返りした際は特に実装工数面の部分は課題感として大きく、そこをまず解決したいという話になりました。
smash.では特に新規の画面や追加仕様が多くあったので、工数が増えるネガティブな意識をメンバーは少なからず持っていました。
この課題解決に導入したのが、Generambaになります。
Generamba
VIPER向けのアーキテクチャ向けのコード生成ツールです。
今回はsmash.ではアーキテクチャは異なりますが、smash.向けのテンプレートを作成し、1コマンドでViewController、View、ViewModel、Usecaseまで作成する仕組みを作成しました。
導入前までは1画面を新規で作成し、build通して確認するまで下手すると1,2時間を要していた箇所を数秒で、作成できるようになりました。
導入後に行った施策ではGenerambaを導入していなければおそらくスケジュールに間に合うことはできませんでした
テンプレート例
実際に利用しているテンプレートとは違いますが、以下のようなテンプレートを用意し、liquidの拡張子がついたファイルを用意することでモジュール単位でのボイラープレートコードを生成することができます。
ViewController.swift.liquid
//
// {{ module_info.name }}ViewController.swift
//
// Created by {{ developer.company }} on {{ date }}.
// Copyright © {{ year }} {{ developer.company }} All rights reserved.
//
import UIKit
final class {{ module_info.name }}ViewController: UIViewController {
}
// MARK: - Lifecycle
extension {{ module_info.name }}ViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
View.swift.liquid
//
// {{ module_info.name }}View.swift
//
// Created by {{ developer.company }} on {{ date }}.
// Copyright © {{ year }} {{ developer.company }} All rights reserved.
//
import UIKit
final class {{ module_info.name }}View: UIView {
}
// MARK: - Setups
extension {{ module_info.name }}View {
func setup() {
}
}
ViewModel.swift.liquid
//
// {{ module_info.name }}ViewModel.swift
//
// Created by {{ developer.company }} on {{ date }}.
// Copyright © {{ year }} {{ developer.company }} All rights reserved.
//
import UIKit
final class {{ module_info.name }}ViewModel: {{ module_info.name }}ViewModel {
private let useCase = Default{{ module_info.name }}UseCase()
}
// MARK: - Lifecycle
extension Default{{ module_info.name }}ViewModel {
func viewWillAppear() {
}
}
UseCase.swift.liquid
//
// {{ module_info.name }}UseCase.swift
//
// Created by {{ developer.company }} on {{ date }}.
// Copyright © {{ year }} {{ developer.company }} All rights reserved.
//
protocol {{ module_info.name }}UseCase {
}
final class Default{{ module_info.name }}UseCase: {{ module_info.name }}UseCase {
init() {
}
}
Makefile
また、smash.ではMakefileを利用しているので、以下のようなコマンドを用意してGenerambaの利用を容易にしています。
Makefile
# ex) g_create_tp_uikit-ModuleName
g_create_tp_uikit-%:
bundle exec generamba gen ${@:g_create_tp_uikit-%=%} ViewTemplateUIKit
例えばVideoPlayerのモジュールを生成する場合
$ make g_create_tp_uikit-VideoPlayer
こちらを実行すると以下の4つファイルを生成することができるようになります
VideoPlayerViewController.swift
VideoPlayerView.swift
VideoPlayerViewModel.swift
VideoPlayerUseCase.swift
以上により、1コマンドでモジュール単位でファイル生成をすることが出来るようになります。
PJごとによってコーディングルールがそれぞれあると思うので、テンプレート的に毎回記載するものもGenerambaのテンプレートにまとめておくと更に効率化できると思います。
課題解決 : 2
>SnapKitで実装してる反面レイアウトが複雑化すると、buildするまでデザインが確認しづらい
SnapKitで実装することでViewの再利用などもしやすくとても便利です。
しかし、特に複雑なデザインであるとコードをさくっと理解することは容易ではありませんでした。
また、実装の簡略化も行いたいということで導入できていなかったSwiftUIの導入を試みました。
まずはいきなり、複雑な画面を実装するのではなくシンプルな画面作成からチームで実装を慣れていくようにしました。
※Xcode15からは#Previewで囲むことでUIKitのプレビューができるのでこの問題は改善できそうです。
最終的なアーキテクチャについて
SwiftUIを考慮したアーキテクチャ
設計
UIはSwiftUIで実装できそうな箇所はSwiftUIをベースに作成
SwiftUIで実装した箇所はCombineを使用
UIKitとSwiftUIを共存する(画面、モジュール単位で分ける)
Coordinatorパターンを採用し、元々ViewControllerベースで画面遷移の処理してるので、UIHostingControllerを継承したViewControllerを作成し、その上にSwiftUIのViewを乗せてSwiftUIの導入の実現させる
設計背景
新規で作成する画面は必ずしも、SwiftUIで作るということではなく、仕様と工数を踏まえた上で状況に合わせてSwiftUIとUIKitどちらも選択できるようにしました。
仕様によってはUIKitで実装した方が機能作成を満たしやすい箇所などがあり、強制はせずチーム内では選べるようにしました。
また、UIKitで実装している箇所はRxSwiftに依存していますが、今後ライブラリ等がメンテナンスが仮にされなくなったとしても運用しやすいように、SwiftUIで実装している箇所からCombineやSwift Concurrencyを導入しやすいようにすることで長期的に運用しやすい環境を検討しました。
Generambaを用いて更に改善
Generambaはテンプレートを複数用意できます。
smash.では以下の2つのテンプレートを用意して運用するようにしました。
ViewTemplateUIKit // UIKitベース
ViewTemplateSwiftUI // SwiftUIベース
MakefileもSwiftUIのものも用意すると以下のような形になります。
# ex) g_create_tp_swiftui-ModuleName
g_create_tp_swiftui-%:
bundle exec generamba gen ${@:g_create_tp_swiftui-%=%} ViewTemplateSwiftUI
まとめ
CleanArchitecture + MVVMで疎結合なアーキテクチャをProduct開始時から意識をしていたので、実装は厳密になり自由度はありません。
しかし、チーム内で統一した実装することができるようになり、レビューの観点がより仕様にフォーカスできました。
統一した実装に関してはGenerambaが更に後押ししてくれました。
また、採用したアーキテクチャは施策を通して振り返りをすることでチーム開発での課題が明確化し、それを少しづつ解決することで、開発しやすい環境ができてきたかなと思います。