iOSエンジニアのためのFirebase: Remote Config編

##【この記事で書くこと】
- Firebaseのサービス（今回はRemote Config）で何ができるのか
- iOS側ではどんなコードを書けば良いのか？
- ただし↑のコードはサンプルコードというより実際に使うために作ったコードなので、分かりにくいかもしれません。

今回はFirebaseの中でも一番シンプルな`Remote Config`について書きたいと思います。

## 【書かないこと】
- Firebaseとは何かとかいう大きな話は書きません
- Firebase Consoleの説明は多く書きません（少なめにとどめる）

## 【環境】
let lang = Swift4.2
let env = XCode10

## 【何ができるのか】

`Remote Config`は、簡単に言うと様々なアプリの設定情報をクラウドで持ち、iOSが（任意のタイミングで何度でも）それを読み込んで使えるというものです。
以下は自分が考えた用途です。

| 用途 | 詳細 | 注意点 |
|-----|-----|-----|
| 画面の配色 |UIView/UIViewControllerの背景色や枠色、文字の色情報を`Remote Config`に設定する。|変更するタイミングは起動時だけなのか、インタラクティブに行うのかを検討する。|
| 画面上の文字 |タイトルや固定の説明文などを`Remote Config`に設定する。| 文字を変更すると所定の領域に収まらないことがあるために要注意|
| メニュー |メニューの内容（文字列）などを`Remote Config`に設定する。|メニューがタップされてからの実際の画面遷移の制御は`Remote Config`ではできないので別途実装すること|
|画面レイアウト|配置情報を`Remote Config`に設定する。画面要素の配置などを変える時に使える（例えば画像と説明文の位置を入れ替えるとか）|上記と同じく配置を変更する実装の難易度は高い（`Remote Config`の範囲外）|
|A/Bテスト|上記の`Remote Config`を使用して変更可能な全ての要素をA/Bテストできる|今回は内容は書きません|

## 【Firebase Console】

Firebase Consoleで`params`というキーでJSONを設定します。
JSONでなくても構いませんが、その場合は後述のparser関数の内容を変えることになります。

![スクリーンショット 2018-10-19 22.42.44.png](https://qiita-image-store.s3.amazonaws.com/0/37634/857b5b41-0327-d9b2-3433-8b6e7d317003.png)

こんなふうに編集することもできます。

![スクリーンショット 2018-10-19 22.42.21.png](https://qiita-image-store.s3.amazonaws.com/0/37634/fe4ff775-d200-b8c4-f28c-3f99499d8571.png)

## 【iOS側のコード】

iOS側のコードは【この記事で書くこと】で書いたように`実際に使うために作ったコード`です。そのため、`Remote Config`のサンプルとしては若干分かりにくいかもしれません。しかし、このような実装が適当であろうという現時点での自分の理解です。

iOS側のコードはCodableを使ってFirebase Consoleで作成した構造RemoteAppConfigurationを定義します。
Firebaseでは`Remote Config`に初期値を設定できますが <font color="DeepPink">注１）</font>、特に意味は無いので記述をシンプルに保つためにRemoteAppConfigurationに初期値を記述しておきます。<font color="DeepPink">注２）</font>

<font color="DeepPink">注１）</font>*remoteConfig.setDefaults(fromPlist: "RemoteConfigDefaults") plistから設定できる（自分はまったくうれしくないので使いません）*
<font color="DeepPink">注２）</font>*初期値を書かなければ各要素はOptionalになるのであまりお勧めできません。が、Firebase Console側で全ての要素を記述しなくてもiOSがクラッシュしないと言うメリットはあります（書き忘れの場合をテスト時に検出したい場合はデメリットか）*

```swift

import Firebase

struct RemoteAppConfiguration: Codable
{
    var one: String = ""
    var two: Int = 0
    var tree: String?
    var four: String = ""
}

```

RemoteConfigをparseするprotocolを定義します。
このprotocolを実装したインスタンスをヘルパークラスに渡すことで、必要なRemoteAppConfigurationなどを取り出してもらいます。

```swift
public protocol RemoteConfigParsable
{
    func parser(remoteConfig: RemoteConfig)
}
```

ヘルパークラスは下記のようなものです。

```swift

public final class RemoteConfigHelper
{
    var remoteConfig: RemoteConfig!
}

public extension RemoteConfigHelper
{
    func confugure(_ remoteConfigurables: [RemoteConfigParsable], expirationDuration: TimeInterval = 20)
    {
        remoteConfig = RemoteConfig.remoteConfig()
        remoteConfig.fetch(withExpirationDuration: expirationDuration) { [weak self]  (status, error) -> Void in
            guard let _self = self else { return }
            if status == .success {
                _self.remoteConfig.activateFetched()
                // 全てのRemoteConfigParsableのparser関数を実行する
                for remoteConfigurable in remoteConfigurables
                {
                    remoteConfigurable.parser(remoteConfig: _self.remoteConfig)
                }
            } else {
                logger.error("RemoteConfig Error: \(error?.localizedDescription ?? "unknown")")
            }
        }
    }
}

```

ヘルパークラスはRemoteConfigParsableを入力とする汎用のクラスになっています。

そしてRemoteConfigParsableの実装です。ここはなんでもいいのですが、parser関数の中で通知処理まで行うと言うちょっと強引な実装にしています。キモはJSONの場合は `JSONDecoder().decode(ResponseType.self, from: data)`と書くこと。他にも文字列だけの場合は、`let str = remoteConfig["key"].stringValue`で取り出したりすることで、ヘルパークラスはparserの内容を知る必要はありません。

```swift

var myConfig: RemoteAppConfiguration = RemoteAppConfiguration()
var myConfigObservable = BwObservable<RemoteAppConfiguration>() // 適当にNotification使うなりなんなりに変更してください

struct InfoConfig: RemoteConfigParsable
{
    public typealias ResponseType = RemoteAppConfiguration
    public let key: String = "params"
    public func parser(remoteConfig: RemoteConfig)
    {
        let data = remoteConfig[key].dataValue
        if let _config = try? JSONDecoder().decode(ResponseType.self, from: data)
        {
            myConfig = _config
            myConfigObservable.publish(myConfig) // 適当にNotification使うなりなんなりに変更してください
            //print(myConfig)
        }
    }
}

```

これで、RemoteConfigParsableの実体を定義するだけでFirebase Consoleで記述した内容をアプリに取り込むことができます。