要件
一ユーザーが複数のグループに所属できるグループチャットが作りたいとします。
FirebaseのDatabaseは下記のようだとします。
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が書き込みのリスト
/*
* このコードが動くようにしたい
* /
*
GroupsCommentsListner(belongGroupIDsObservable).listen().subscribe{ allGroupsComments in
print(allGroupsComments.element!)
/*
* [
* "group0": ["comment0-0", "comment0-1", "comment0-2"],
* "group1": ["comment1-0", "comment1-1", "comment1-2"]
* ]
* /
}
よくある要件だと思いますが、正直自分もこの方法がベストだと思えないので、対案募集がてら投稿します。
実装の概要
- 流れてきた所属先リストをfor文で回し、全てのグループにコネクションを貼る
- この際、Firebaseのライブラリからhandleというコネクションの識別子が渡されるのでそれを控えておく
- 書き込みが流れてきたらPublishSubjectに送信する
- RxSwiftのScan関数を使って2のPublishSubjectを全てのグループの書き込みの辞書に変換する
できたコード
呼び出し側
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!)
}
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!