iOSアプリのローディングライブラリをリプレースする
はじめに
こんにちは、iOS エンジニアの長(ちょう)です。
ウェルスナビでは技術的負債の解消の取り組みとして、
CocoaPods から SwiftPackagerManager への移行を進めています。
その取り組みの一環として、
ローディング表示に利用しているライブラリを独自実装に置き換えましたので紹介します。
CocoaPods/SwiftPackageManager について
CocoaPods とは
CocoaPods は、Objective-C および Swift のライブラリやフレームワークを管理を目的としたツールです。パッケージのバージョン管理や依存関係の解決にも対応しており、開発の効率を向上させることができます。
一方で、開発の進化に伴いいくつかの課題が浮き彫りになっています。
例として、CocoaPods は Apple が公式にサポートしているツールではなく、何らかの理由で利用できなくなった場合にアプリをリリースできなくなるリスクが存在します。
また、Ruby の gem
を利用してインストールするため、環境によってライブラリのインストールに失敗したり、予期せぬエラーが発生するなどいくつかの技術的負債となりうる特徴も持ち合わせます。
SwiftPacakgeManager とは
SwiftPackageManager(以下、SPM)は、Swift コードの配布を管理するためのツールです。
SPM は、Apple が公式にサポートしているツールであるため、前述のアプリをリリースできなくなるリスクが低くなります。
また、CocoaPods と異なり、Ruby, gem といった追加の環境構築およびインストール不要で利用できます。
iOS アプリ開発が Swift から別の言語に移行しない限り、SPM の利用が技術的負債になる可能性は限りなく低いと考えられます。
独自実装への置き換えを決めるまで
SVProgressHUD とは
SVProgressHUD とは、iOS アプリでよく見かけるローディング画面やメッセージ表示などを簡単に実装できるライブラリです。
ウェルスナビでは、このライブラリを利用していましたが、最近になっていくつかの問題点が見えてきました。
SVProgressHUD を使い続けるうえでの課題
SwiftPackageManager に対応していない
SVProgressHUD は、2023 年 8 月時点で SPM に対応していませんでした。
これは、CocoaPods を使い続ける理由の一つになっていました。
CocoaPods は、依存関係の管理やビルドの設定などを自動化してくれる便利なツールですが、前述の通り技術的負債にもなり得ます。
また、ライブラリ管理が SPM と CocoaPods の二重管理となっており、プロジェクトの構成が複雑になっていました。
OSS を利用することの弊害
SVProgressHUD は、オープンソースソフトウェア(OSS)として公開されているライブラリです。
OSS は、コミュニティによって開発やメンテナンスが行われるため、多くのメリットがあります。
しかし、OSS に依存することにもデメリットがあります。 例えば、ライブラリの更新が滞ったり、開発者が変わったり、ライセンスが変更されたりすることがあります。この場合、ライブラリの移行や対応に時間や労力を割く必要があります。
また、ライブラリの仕様や実装に不満がある場合にも、自由に変更できるとは限りません。
解決策
上記の課題を解決するために、以下のアプローチを検討しました。
1. SVProgressHUD を Fork して、SPM に対応させて使い続ける
最初の解決策は、SVProgressHUD のソースコードを自分たちのリポジトリにコピーして、SPM の設定ファイルを追加するというものです。
これは、比較的簡単に実現できる方法ですが、SVProgressHUD のオリジナルのリポジトリとの同期を取る必要があります。
また、SVProgressHUD のライセンスに従って、Fork したことを明示する必要があります。
2. 別の OSS ライブラリに乗り換える
次の解決策は、SVProgressHUD と同様の機能を提供する別の OSS ライブラリに乗り換えるというものです。
これは、SVProgressHUD の仕様や実装に不満がある場合に有効な方法ですが、移行に伴うコードの変更やテストの必要性があります。
また、別の OSS ライブラリにも同様の課題が発生する可能性があります。
3. 独自実装に置き換える
最後の解決策は、SVProgressHUD を独自実装に置き換えるというものです。
これは、SVProgressHUD に依存しないことで、技術的負債やリスクを減らすことができる方法ですが、実装にかかる時間や労力が最も多い方法です。
また、独自実装にもバグや不具合が発生する可能性があります。
独自実装への置き換え
上記の解決策の中から、3. 独自実装への置き換えを選択しました。その理由は、以下の通りです。
- アプリで利用している SVProgressHUD の機能は、Swift の標準機能で代替可能である
- 独自実装によって、ライブラリの仕様や実装に自由度を持てる
- OSS が更新されなくなった場合に、移行コストが発生しない
- OSS が更新されなくなった場合に、アプリをリリースできなくなるリスクが排除できる
独自実装への移行
独自実装を進めるにあたり、以下の手順を踏んで移行を進めました。
以下、独自実装は、WNProgressHUD と呼ぶことにします。
- SVProgressHUD で実現している要件の整理
- SVProgressHUD を利用している実装の洗い出し
- WNProgressHUD の設計
- WNProgressHUD の実装
要件の整理
まず、SVProgressHUD で実現している要件を整理しました。
- API リクエスト開始時にローディング表示
- API リクエスト終了時にローディング非表示
- API リクエスト中に必要に応じてユーザの操作を無効化
- API リクエスト終了時に任意の処理を実行
上記の 1.と 2.は API からデータ取得を開始したことと終了したことをユーザに知らせるのを目的としています。
また、3.は API リクエスト中にユーザが操作することで、アプリの状態が不整合になることを防ぐためです。
4. は、API リクエスト終了時に任意の処理を実行することで、API リクエストの結果に応じた処理を行うためです。
実装の洗い出し
続いて、SVProgressHUD を利用している実装を洗い出しました。
WealthNavi アプリでは、SVProgressHUD を別のクラスでラップして、以下のようなインターフェースを提供していました。
// ローディング表示(ユーザの操作無効)
static func showWithoutUserInteraction() {
// 省略
}
// ローディング表示
static func show(maskType: SVProgressHUDMaskType = .none, bgColor: UIColor = .clear) {
// 省略
}
// ローディング非表示
static func dismiss() {
// 省略
}
// ローディング非表示(非表示後に任意の処理実行)
static func dismiss(with completion: SVProgressHUDDismissCompletion?) {
// 省略
}
よって、上記のインターフェースはそのままに、内部実装を WNProgressHUD に置き換えることで移行ができそうです。
設計
SVProgressHUD 利用時の課題として、アプリケーション内の意図しないwindow
にローディングが表示されてしまうことがありました。
その結果、意図せずローディングが消えてしまうなどの現象が見られました。
そこで、以下の URL を参考にローディング表示専用のwindow
を生成し、そのwindow
内にローディングに必要なview
を配置する設計としました。
参考:Swift UIWindow 活用術 〜やめよう!最前画面の取得〜
また、ウェルスナビが提供している他のアプリケーションでも SVProgressHUD を利用しており、
再利用性を高めるために Swift Package として実装する方針としました。
実装
設計を元に、WNProgressHUD を実装しました。
UIWindow
> UIHostingController
> View
(SwiftUI) という構造になっています。
これは、現在 SwiftUI への移行を進めており UIKit との互換性を保つためです。
また、ローディングインジケータ(ぐるぐる回るアイコン)は SwiftUI のProgressView
を利用しました。
SwiftUI - ProgressView
このProgressView
は色やインジケータのサイズなどをカスタマイズできます。
前述のとおり、他アプリケーションでの利用を考慮して、色をカスタマイズできるように実装しました。
struct WNIndicatorView: View {
let opacity: Double // 透過度 1.0: 不透明 0.0: 透明
let color: Color // ローディングインジケータの色
let bgColor: Color // 背景の色
// 省略
init(color: Color = .accentColor, bgColor: Color = .gray, opacity: Double = 1.0) {
self.opacity = opacity
self.color = color
self.bgColor = bgColor
}
// 省略
}
WNProgressHUD で得られた成果
WNProgressHUD によって、SVProgressHUD 利用時と同等の機能を提供できました。また、独自実装にしたことで、以下のようなメリットが得られました。
- SPM に対応
- CocoaPods からの脱却
- ライブラリの仕様や実装をカスタマイズ可能
- アプリ公開できないリスクを排除
まとめ
今回は、SVProgressHUD というライブラリを独自実装に置き換えた経緯と方法について紹介しました。
独自実装にすることで、技術的負債やリスクを減らすことができました。
もちろん、独自実装は高い実装コストが必要、不具合混入の可能性が高まるなどのデメリットや課題がありますが、開発メンバーが支払えるコストで運用できるようになりました。上記のデメリットや課題は今後の開発でさらに改善していきます。
SVProgressHUD を使っている方や、OSS ライブラリに依存している方は参考になれば幸いです。
ご質問やご意見がありましたら、コメント欄にお願いします。ありがとうございました。
あとがき
この記事を書いている最中に SVProgressHUD が SPM に対応しました。
https://github.com/SVProgressHUD/SVProgressHUD#swift-package-manager
明日は、HRBP 佐藤 の「開発組織をエンゲージメントするHRBPとしての取り組み」です!
お楽しみに!
📣 ウェルスナビは一緒に働く仲間を募集しています 📣
https://hrmos.co/pages/wealthnavi/jobs?category=1243739934161813504
著者プロフィール
長 俊貴(ちょう としき)
通信会社でモバイルアプリ開発や位置測位の新規技術開発に従事したのち、
2023 年 06 月に iOS アプリエンジニアとしてウェルスナビに入社。
入社後は iOS アプリ開発を主たる業務としつつ、
ウェルスナビ開発者ブログの運営や、ブログ執筆環境の整備にも取り組む。
Discussion