はじめに
リアルタイム更新に対応したFirebase RemoteConfigを導入したくドキュメントを読みながら実装しました。
環境
Xcode 14.3
内容
Firebaseはセットアップ済みの状態を想定しRemoteConfig部分のみまとめていきます。
RemoteConfigとは
Firebase Remote Config を使ってアプリ内パラメータを定義し、その値をクラウドで更新できます。これにより、アプリのアップデートを配布しなくてもアプリの外観や動作を変更できます。
設定できる値には以下のような制限がある
パラメータ値のデータ型
String
Boolean
Number
JSON
パラメータと条件の制限
- 最大
2,000
個のパラメータ、最大500
個の条件を設定できる - パラメータキーの長さは最大
256
文字 - プロジェクト内のパラメータ値文字列の合計長は、
1,000,000
文字以内にする必要がある
基本はドキュメント通りやっていきます
Remote Config をアプリに追加
まずはSPMを使ってFirebaseRemoteConfigSwift
を取得
RemoteConfigを扱うクラスにimport
import FirebaseRemoteConfig
import FirebaseRemoteConfigSwift
Remote Config シングルトン オブジェクトを作成
remoteConfig = RemoteConfig.remoteConfig()
let settings = RemoteConfigSettings()
settings.minimumFetchInterval = 0
remoteConfig.configSettings = settings
ここで設定するminimumFetchInterval
とはRemoteConfigの値を取得する間隔ですが、リアルタイム更新に対応すれば不要になる
Apple platforms SDK v10.7.0 以降では、リアルタイムの Remote Config を使用して、更新されたパラメータ値が公開されるとすぐに Remote Config バックエンドから自動的にフェッチできます。これにより、最小フェッチ間隔の設定が不要になります。
フェッチ間隔の優先順位は以下の順番
- fetch(long) のパラメータ
- FIRRemoteConfigSettings.MinimumFetchInterval のパラメータ
- デフォルト値(12 時間)
サンプルコードのセットアップ部分を抜き出すとこのような形
import FirebaseRemoteConfig
import FirebaseRemoteConfigSwift
class RemoteConfigViewController: UIViewController {
private var remoteConfig: RemoteConfig!
private var remoteConfigView: RemoteConfigView
/// Convenience init for injecting Remote Config instances during testing
/// - Parameter remoteConfig: a Remote Config instance
convenience init(remoteConfig: RemoteConfig) {
self.init()
self.remoteConfig = remoteConfig
}
override func viewDidLoad() {
super.viewDidLoad()
setupRemoteConfig()
}
/// Initializes defaults from `RemoteConfigDefaults.plist` and sets config's settings to developer mode
private func setupRemoteConfig() {
remoteConfig = RemoteConfig.remoteConfig()
let settings = RemoteConfigSettings()
settings.minimumFetchInterval = 0
remoteConfig.configSettings = settings
...
}
}
アプリ内デフォルトパラメータ値を設定する
この対応は必須ではないのでスキップでもOK
NSDictionary
またはplist
を使用して、一連のパラメータ名とデフォルトパラメータ値を定義しておくと、Remote Configバックエンドに接続する前もしくは値が設定されていない場合にデフォルト値を使用できる。
plistはRemoteConfigのコンソールから取得できる
アプリ内でパラメータ値がgetメソッドによって返される優先順位
- バックエンドから値がフェッチされ、有効化された場合、アプリはそのフェッチされた値を使用します。有効化されたパラメータ値は永続的です。
- バックエンドから値がフェッチされなかった場合、または Remote Config バックエンドからフェッチされた値が有効化されていない場合、アプリはアプリ内デフォルト値を使用します。
- アプリ内デフォルト値が設定されていない場合、アプリは静的型の値(int の場合は 0、boolean の場合は false など)を使用します。
サンプルコードのserDefaults
部分
private func setupRemoteConfig() {
remoteConfig = RemoteConfig.remoteConfig()
// This is an alternative to remoteConfig.setDefaults(fromPlist: "RemoteConfigDefaults"))
do {
try remoteConfig.setDefaults(from: QSConfig(topLabelKey: "myTopLabel",
bottomLabelKey: "Buy one get one free!",
freeCount: 4))
} catch {
print("Failed to set Defaults.")
}
...
}
値をフェッチして有効にする
Remote Config からパラメータ値をフェッチするには、fetchWithCompletionHandler:
またはfetchWithExpirationDuration:completionHandler:
メソッドを呼び出す。バックエンドに設定したすべての値がフェッチされ、Remote Config オブジェクトにキャッシュ保存される。
1 回の呼び出しで値をフェッチして有効化する場合は、fetchAndActivateWithCompletionHandler:
を使用します。
remoteConfig.fetch { (status, error) -> Void in
if status == .success {
print("Config fetched!")
self.remoteConfig.activate { changed, error in
// ...
}
} else {
print("Config not fetched")
print("Error: \(error?.localizedDescription ?? "No error available.")")
}
self.displayWelcome()
}
3つのfetchメソッド👇
-
func fetch() async throws -> RemoteConfigFetchStatus
データを取得。activate()
することで取得したデータがアプリで使用できる -
func fetch(withExpirationDuration expirationDuration: TimeInterval) async throws -> RemoteConfigFetchStatus
データを取得し、設定データの存続期間を指定する期間を設定。activateWithCompletion:
することで取得したデータがアプリで使用できる -
func fetchAndActivate() async throws -> RemoteConfigFetchAndActivateStatus
データを取得し、成功した場合は取得したデータをアクティブにします。フェッチ呼び出しが成功した場合、データのアクティブ化を試行した後、オプションの完了ハンドラー コールバックが呼び出される
ドキュメントとは別ですが、サンプルコードの実装はこのような形になっていた
func fetchAndActivateRemoteConfig() {
remoteConfig.fetchAndActivate { status, error in
guard error == nil else { return self.displayError(error) }
print("Remote config successfully fetched & activated!")
do {
let qsConfig: QSConfig = try self.remoteConfig.decoded()
print(qsConfig)
} catch {
self.displayError(error)
return
}
DispatchQueue.main.async {
self.updateUI()
}
}
}
リアルタイムで更新をリッスンする
パラメータ値をフェッチしたら、リアルタイム更新を使用して、バックエンドからの更新をリアルタイムでリッスンできる
remoteConfig.addOnConfigUpdateListener { configUpdate, error in
guard let configUpdate, error == nil else {
print("Error listening for config updates: \(error)")
}
print("Updated keys: \(configUpdate.updatedKeys)")
self.remoteConfig.activate { changed, error in
guard error == nil else { return self.displayError(error) }
DispatchQueue.main.async {
self.displayWelcome()
}
}
}
ほぼほぼドキュメントと変わりませんが、サンプルコードでこのような実装になっていた
/// Initializes defaults from `RemoteConfigDefaults.plist` and sets config's settings to developer mode
private func setupRemoteConfig() {
remoteConfig = RemoteConfig.remoteConfig()
// This is an alternative to remoteConfig.setDefaults(fromPlist: "RemoteConfigDefaults"))
do {
try remoteConfig.setDefaults(from: QSConfig(topLabelKey: "myTopLabel",
bottomLabelKey: "Buy one get one free!",
freeCount: 4))
} catch {
print("Failed to set Defaults.")
}
let settings = RemoteConfigSettings()
settings.minimumFetchInterval = 0
remoteConfig.configSettings = settings
// [START add_config_update_listener]
remoteConfig.addOnConfigUpdateListener { configUpdate, error in
guard error == nil else { return self.displayError(error) }
print("Updated keys: \(configUpdate!.updatedKeys)")
self.remoteConfig.activate { changed, error in
guard error == nil else { return self.displayError(error) }
DispatchQueue.main.async {
self.updateUI()
}
}
}
// [END add_config_update_listener]
}
おわりに
もともとアプリの運用で非常に便利であったRemoteConfigですが、リアルタイム更新が可能になったことで、より活用の幅が広がったのかなと思います!セットアップも簡単なので個人開発アプリなどでも初回リリースから入れていきたいところです
それと今回ドキュメントを読んでいてRemote Config の更新公開時に Slack /メール メッセージを送信する
方法について書かれていた部分があり、これは業務でも使えそうだな〜と思ったのでこの部分はあらためて読んでみたいと思います
参考