ListenerRegistrationとは
- WebSocketを利用してデータ更新を行うリアルタイムリスナー
- Firebaseに更新があれば非同期でその更新を受け取ることが可能
- Firebaseはリアルタイムでネットワーク通信を行う時にその裏側を意識しなくても使える便利な機能(ポーリング不要、定期取得不要)
- データベースのアップデートに使うと便利
例:Firebase databaseのCloud Fire Stormをチャットアプリを作る場合を想定
FirestoreDatabaseにデータベースを以下のような形で持つとする

Create:リスナーは使わない
データのパス
enum FCollectionReference:String{
case User
case Recent
case Message
case Typing
case Channel
}
func FirebaseReference(_ collectionReference:FCollectionReference) -> CollectionReference{
return Firestore.firestore().collection(collectionReference.rawValue)
}
Create
//MARK: - Add
func addMessage(_ message:LocalMessage,memberId:String){
do{
let _ = try Firestore.firestore().collection(FCollectionReference.Message.rawValue).document(memberId)
.collection(message.chatRoomId).document(message.id).setData(from: message)
}catch{
print("error saving",error.localizedDescription)
}
}
砕いて書くと以下
func addMessage(_ message:LocalMessage,memberId:String){
do{
let _ = try Firestore.firestore().collection("FilePath").document(memberId)
.collection(message.chatRoomId).document(message.id).setData(from: message)
}catch{
print("error saving",error.localizedDescription)
}
}
Collection→documentでディレクトリのフォルダパスのようなイメージ、そこに対してsetDataを行うとドキュメントの追加がなされる。
さらに階層をつけにディレクトリを作る場合は
Collection→document、Collection→document そこに対してsetData(data)を入れるとすれば、
トップフォルダ→フォルダ1→フォルダ2→フォルダ3→フォルダ3にデータを入れる。
こんなイメージをすればよい
Read:現在のFirebaseのデータを読み取る(リスナーは使わない)
//Firebaseにmessageがあるか確認してあったらRealmに保存する
//保存した後にRealmのデータをチャットに表示する
func checkForOldChats(_ documentId:String,CollectionId:String){
FirebaseReference(.Message).document(documentId).collection(CollectionId).getDocuments { snapshot, error in
guard let documets = snapshot?.documents else {
print("no documents for old chats")
return
}
//realmにデータを保存するデータをcompactMapでデコードしてLocalMessageで取得する
var oldMessages = documets.compactMap {
//クロージャの引数とクロージャの戻り値
(QueryDocumentSnapshot) -> LocalMessage? in
return try? QueryDocumentSnapshot.data(as: LocalMessage.self)
}
//並び替え
oldMessages.sort(by: {$0.date < $1.date})
for message in oldMessages{
RealmManager.shared.saveToRealm(message)
}
}
}
砕いて書くと
//保存した後にRealmのデータをチャットに表示する
func checkForOldChats(_ documentId:String,CollectionId:String){
Firestore.firestore().collection("FilePath").document(documentId).collection(CollectionId).getDocuments { snapshot, error in
//snapshotに取得したデータが保存されている。nilであればreturn
guard let documets = snapshot?.documents else {
print("no documents for old chats")
return
}
//realmにデータを保存するデータをcompactMapでデコードしてLocalMessageで取得する
var oldMessages = documets.compactMap {
//クロージャの引数とクロージャの戻り値
(QueryDocumentSnapshot) -> LocalMessage? in
return try? QueryDocumentSnapshot.data(as: LocalMessage.self)
}
//古いものが上にくるように並び替え
oldMessages.sort(by: {$0.date < $1.date})
for message in oldMessages{
RealmManager.shared.saveToRealm(message)
}
}
}
結局これはgetDocumentsを使っているにすぎない
Update:これが重要でリスナーを使って行う
①リスナーを使わない場合
//MARK: - UpdateMassageStatus
func updateMessageInFirebase(_ message:LocalMessage,memberIds:[String]){
let values = [kSTATUS:kREAD,kREADDATE:Date()] as [String:Any]
for userId in memberIds{
//FirebaseのchatRoomIdの対象のメッセージのデータをアップデートする。
FirebaseReference(.Message).document(userId).collection(message.chatRoomId).document(message.id).updateData(values)
}
}
②リスナーを使う場合
チャットアプリでaddSnapshotListerで指定したパスのメッセージの変更を取得する
//update
//新しいメッセージを受信して取得する。Firebaseのデータを受信してdb(Realm)に反映する。
//dbに変更があったときにローカルで表示を変更するがそれはloadChatsでRealmのリスナーを作成して反映させている
func listenForNewChats(_ documentId:String,CollectionId:String,lastMessageDate:Date){
//ローカルに持っているmessageの最後の日付より新しいデータを取得する
//addSnapshotListenerで指定したパスのメッセージの変更を取得するListenerを取得出来る
newChatListener = FirebaseReference(.Message).document(documentId).collection(CollectionId).whereField(kDATE, isGreaterThan: lastMessageDate).addSnapshotListener({ snapshot, error in
//snapshotを取得して空であればここでreturn
guard let snapshot = snapshot else {
return
}
//snapshotのdocumentChangesでtypeが追加(.added)の時は新しいデータがFirebaseにあるのでローカルデータベースにも追加
for change in snapshot.documentChanges{
if change.type == .added{
let result = Result{
try? change.document.data(as:LocalMessage.self)
}
switch result {
case .success(let messageObject):
if let message = messageObject{
//メッセージを送信するときにRealm(ローカルdb)に保存しているので、送信者が自分の時はRealmを更新する必要はない
if message.senderId != User.currentId{
RealmManager.shared.saveToRealm(message)
}
}else{
print("Document dosent exist")
}
case .failure(let error):
print("Error decoding local message \(error.localizedDescription)")
}
}
}
})
}
addSnapshotListenerが重要でListenerを設定したオブジェクトに変化があるとクロージャが実行されて、メッセージの変更をアプリが検知することが出来て、メッセージの受信をアプリでリアルタイムで取得できる
Delete:Firebase上のデータ削除
func deleteRecent(_ recent:RecentChat){
FirebaseReference(.Recent).document(recent.id).delete()
}
まとめ
ListenerRegistrationを使うとCRUDのUの部分が自動で受信されるのでチャットアプリなどで便利
全体通してちゃんとした説明になってなくてすいません。。これを噛み砕いて説明できるようになりたい。
コメント