Firebaseでグループチャットを作る時、所属する全てのグループの書き込みを一つのストリームで受け取る

# 要件
一ユーザーが複数のグループに所属できるグループチャットが作りたいとします。
FirebaseのDatabaseは下記のようだとします。

```json:database.json

{
  "Groups" : {
    "group0" : [ "comment0-0", "comment0-1", "comment0-2" ],
    "group1" : [ "comment1-0", "comment1-1", "comment1-2" ],
    "group2" : [ "comment2-0", "comment2-1", "comment2-2" ]
  },
  "Users" : {
    "user1" : [ "group0", "group1" ]
  }
}
```
user1の場合/User/user1 の配列にグループ名を追加すればそのグループに所属したことになるとします。


# 課題
iOSアプリを書く際に困る点として、所属するグループが動的に変化するためその変化に合わせてDatabaseのListen先を切り替えなければいけないことが挙げられます。
そこで所属先リストのObserverを引数に所属している全グループの書き込みをObserveするクラスを作りたいです。

 - 引数：belongGroupIDsObservable
    - ユーザーが所属しているグループのIDのリストのObserver
    - 型はObservable<[String]>
 - 戻り値：下記書き込み辞書のObserver
 - 書き込み辞書：allGroupsComments
    - ユーザーが所属しているグループの書き込みの辞書
    - 型は[String:[String]]
    - keyがGroupIDでvalueが書き込みのリスト



```swift
/*
 * このコードが動くようにしたい
 * /
 * 
GroupsCommentsListner(belongGroupIDsObservable).listen().subscribe{ allGroupsComments　in
  print(allGroupsComments.element!)
 /*
  * [
  *   "group0": ["comment0-0", "comment0-1", "comment0-2"],
  *   "group1": ["comment1-0", "comment1-1", "comment1-2"]
  * ]
  * /
}

```

よくある要件だと思いますが、正直自分もこの方法がベストだと思えないので、対案募集がてら投稿します。

# 実装の概要

 1. 流れてきた所属先リストをfor文で回し、全てのグループにコネクションを貼る
    1. この際、Firebaseのライブラリからhandleというコネクションの識別子が渡されるのでそれを控えておく
 2. 書き込みが流れてきたらPublishSubjectに送信する
 3. RxSwiftのScan関数を使って2のPublishSubjectを全てのグループの書き込みの辞書に変換する
 
# できたコード

## 呼び出し側
```swift:main.swift

        FIRApp.configure()
        let belongGroupIDsObservable = PublishSubject<[String]>()
        let ref = FIRDatabase.database().reference()
        ref.child("/Users/user1").observe(.value, with: { event in
            
            guard let value = event.value as? [String] else {
                return
            }
            belongGroupIDsObservable.onNext(value)
        })
        
        GroupsCommentsListener(belongGroupIDsObservable).listen().subscribe{ allGroupsComments in
            print(allGroupsComments.element!)
        }

```

```swift:listener.swift
class GroupsCommentsListener {
    
    // ユーザーが所属している全てのグループの全ての書き込み
    //[GroupID:[Comment]]
    var allGropusComments:Variable<[String:[String]]>
    // ユーザーの所属しているGroup
    //このvalueを変更したら自動的にFirebaseListenerをつなぎ直す事が目標
    var belongGroupIDs:Observable<[String]>
    // FirebaseのGroupをListenしたときに渡されるHandleを保存しておく
    var fbCommentsListenerHandles:[String:UInt] = [:]
    
    init(_ belongGroupIDs:Observable<[String]>) {
        allGropusComments = Variable([:])
        self.belongGroupIDs = belongGroupIDs
        subscribeBelongs()
    }
    
    func listen() -> Observable<[String:[String]]> {
        return allGropusComments.asObservable()
    }
    
    // ユーザーの所属グループの変化を監視する
    // 変化があったら全てのCommentsへの接続をクリアし、再度接続しなおす
    func subscribeBelongs() {
        belongGroupIDs.subscribe{
            self.removeListeners()
            guard let value = $0.element as? [String]? , let value2 = value else {
                return
            
            }
            self.setCommentsListener(currentBelongs: value2)
        }
    }
    
    
    // Groupへの書き込みをListenする
    // 書き込みイベントを一本のObserverにまとめる
    // この時scanメソッドを使う
    func setCommentsListener(currentBelongs:[String]) {
        let ps = PublishSubject<[String:[String]]>()
        
            let ref = FIRDatabase.database().reference()
        
            for belong in currentBelongs {
                let handle = ref.child("/Groups/\(belong)").observe(.value, with: { event in
                    ps.onNext([belong:event.value! as! [String]])
                })
                self.fbCommentsListenerHandles[belong] = handle
            }
        
        
        ps
            .scan([:], accumulator: { acum, elem in
                var newDic = acum
                let key = elem.keys.first!
                let value = elem[key]!
                newDic[key] = value
                return newDic
            })
            .subscribe{self.allGropusComments.value = $0.element! as! [String:[String]]}
    }
    
    // Firebaseの監視の解除
    func removeListeners(){
        let ref = FIRDatabase.database().reference()
        for (groupID,handle) in fbCommentsListenerHandles {
            ref.child("Groups/\(groupID)").removeObserver(withHandle: handle)
        }
        fbCommentsListenerHandles = [:]
    }
}

```

とりあえずうごきました。
もう少し良い実装方法を思いついた方Wanted!