Swift+Firebase:存储、检索和使用文档引用数组



我对Swift和Firebase比较陌生,所以我不太熟悉两者如何协同工作的复杂性。我正在构建一个包含消息和线程的聊天应用程序。用户可以发送一条消息struct Message,如果另一个用户想直接回复该消息,就会创建一个线程。对于每条消息,我将存储一个Firebase文档引用数组,这些引用指向线程threadBefore: [Message]中的其他消息。

struct Message: Codable, Identifiable {
var id: String
var content: String
var name: String
var upvotes: Int
var likedByUser: Bool
var dontShow: Bool
var sentAt: Date
var threadsArray: [Message]
}

以下是我从Firebase获取所有聊天消息的代码:dontShow属性:如果dontShow == true表示消息在线程中,不应该像聊天中的常规消息一样显示。但是,线程中的最后一条消息会显示出来,并且具有dontShow = false

func fetchMessages(docId: String, collectionType: String, isThreadMember: Bool) {
if (user != nil) {
db.collection("chatrooms").document(docId).collection(collectionType).order(by: "sentAt", descending: false).addSnapshotListener { (snapshot, error) in
guard let documents = snapshot?.documents else {
print("No messages")
return
}

//                let threadsTemp: [Message]()
let classroomId = docId
if !isThreadMember {
if collectionType == "messages" {
self.messages = documents.map { doc -> Message in
let data = doc.data()
let docId1 = doc.documentID
let content = data["content"] as? String ?? ""
let displayName = data["displayName"] as? String ?? ""
let likedUsersArr = data["likedUsers"] as? Array ?? [""]
// if message is in thread (but not last message), then don't show as normal message, but in thread
let dontShow = data["dontShow"] as? Bool ?? false
let sentAt = data["sentAt"] as? Date ?? Date()
let threadBefore = data["threadBefore"] as? [DocumentReference] ?? [DocumentReference]()

// using reference array
if dontShow == false {
if (threadBefore.count > 0) {
// reset the temporary array that holds the threads to be added afterwards
self.threadsTemp = []
for docRef in threadBefore {
docRef.getDocument { (doc2, error) in
if let doc2 = doc2, doc2.exists {
let docId2 = doc2.documentID
self.fetchThreadMessages(classroomId: classroomId, parentMessageId: docId1, docId: docId2)
} else {
print("Document does not exist")
}
}
}
}
}
return Message(id: docId1,
content: content,
name: displayName,
upvotes: likedUsersArr.count,
likedByUser: likedUsersArr.contains(self.user!.uid) ? true : false,
dontShow: dontShow,
sentAt: sentAt,
threadsArray: self.threadsTemp)
}
}

另一个功能:fetchThreadMessages:

// fetch a specified message and then append to the temporary threads array, threadsTemp
func fetchThreadMessages(classroomId: String, parentMessageId: String, docId: String) -> Message {
if (user != nil) {
let docRef = db.collection("chatrooms").document(classroomId).collection("messages").document(docId)
docRef.getDocument { (doc, error) in
if let doc = doc, doc.exists {
if let data = doc.data(){
let docId = doc.documentID
print("docid")
print(docId)
let content = data["content"] as? String ?? ""
let displayName = data["displayName"] as? String ?? ""
let likedUsersArr = data["likedUsers"] as? Array ?? [""]
// if message is in thread (but not last message), then don't show as normal message, but in thread
let dontShow = data["dontShow"] as? Bool ?? false
let sentAt = data["sentAt"] as? Date ?? Date()

self.threadsTemp.append(Message(id: docId,
content: content,
name: displayName,
upvotes: likedUsersArr.count,
likedByUser: likedUsersArr.contains(self.user!.uid) ? true : false,
dontShow: true,
sentAt: sentAt,
threadsArray: []))
}
}
}
}
}

我还没有实现sendMessage()函数如何更新threadBefore数组,但我目前正在Firebase上直接更新此字段,只是为了测试。

func sendMessage(messageContent: String, docId: String, collectionType: String, isReply: Bool, threadId: String) {
if (user != nil) {
if isReply {
let docRef = db.collection("chatrooms").document(docId).collection(collectionType).document(threadId)
self.threadRef = db.document(docRef.path)
}
db.collection("chatrooms").document(docId).collection(collectionType).addDocument(data: [
"sentAt": Date(),
"displayName": user!.email ?? "",
"content": messageContent,
"likedUsers": [String](),
"sender": user!.uid,
"threadBefore": isReply ? [threadRef] : [DocumentReference](),
"dontShow": false])
}
}

关于如何从threadsBefore获取和检索文档引用的更多信息:对于集合中的每个message,我将其由DocumentReferences组成的threadsArray循环到该线程中的其他消息。对于这些文档引用中的每一个,我都运行self.fetchThreadMessages。这将检索该消息并将message((实例存储在threadsTemp中。然后,回到self.fetchMessage,当我用从threadsBefore检索到的所有文档填充完self.threadsTemp后,我将其存储在Message结构的threadsArray属性中。

现在,看看上面self.fetchMessages中的返回状态,Message()中的最后一个赋值是threadsArray: self.threadsTemp。但这里的问题是,这只是一个参考?它会根据上次分配给self.threadsTemp而改变吗?

我已经尝试了多种方法来实现整个存储和检索功能。但这一切都伴随着几个复杂的错误。我尝试使用字典,或者只存储线程消息的文档id,然后在self.Messages中查找它们(因为它存储了所有消息(。

实现这一点的最佳方式是什么?或者修复我的错误?我知道我的代码可能是低效和混乱的编码实践的混合体。但我正在努力学习。

话虽如此(也就是说,我在上面的评论(,我不会像提出我认为是为聊天应用程序组织Firestore数据库的好方法那样多地处理您的代码。然后,希望你能把它应用到你自己的情况中。

首先,我们必须记住,Firestore是根据你的查询次数收费的——它没有考虑到在特定查询中提取的数据量——所以提取一个单词和提取一整本小说一样昂贵。因此,我们应该以经济高效的方式构建数据库。

我个人要做的是用两个主要部分来构建我的数据库,(1(对话线程和(2(用户。每个用户都包含对其所属线程的引用,每个线程都包含对该线程中用户的引用。这样,我们就可以轻松有效地获取用户的对话,或者获取特定对话中的用户。

// COL = Collection
// DOC = Document
Threads (COL)
Thread1 (DOC)
id: String // id of the thread
participants: [String: String] = [
user1: [
"username": "Bob23",
"userID": IJ238KD... // the document id of the user
]
] // Keep this "participants" dictionary in the root of the thread 
// document so we have easy access to fetch any user we want, if
// we so desire (like if the user clicks to view the profile of a
// user in the chat.
Messages (COL)
Message1 (DOC)
from: String // id of user that sent message
Message2 (DOC)
...
...
Thread 2 (DOC)
...
...
Users (COL)
User1 (DOC)
threadIDs: [String] // Contains the thread "id"s of all the threads
// the user is a part of. (You probably want to 
// use a dictionary instead since array's can be
// difficult to work with in a database, but for
// simplicity, I'm going to use an array here.)
User2 (DOC)
...
...

现在,假设用户打开应用程序,我们需要获取他们的对话线程。它和一样简单

db.collection("Threads").whereField("id", in: user.threadIDs)

[EDIT]user.threads将是类型为String的本地数组。您应该手头有这些数据,因为您将在应用程序启动时获取当前用户的User文档,这就是为什么我们在该用户文档中包含这些数据

基本上,该查询返回Threads集合中的每个文档;id";字段存在于用户的threadID数组中(这是一个String类型的数组(。您可以在这里的文档中阅读更多关于Firestore查询的信息。

换句话说,我们获取用户引用的每个线程文档

这样做的好处在于,只需要一个查询就可以返回用户的所有对话。

现在假设用户在应用程序中滚动他们的对话线程,然后点击其中一个线程打开消息。现在,我们所做的就是获取该特定线程中的所有消息,同样只需要一个查询即可

最后但并非最不重要的是,如果出于某种原因,我们必须在对话中获取有关特定用户的信息,如果需要,我们会在线程文档本身中获得所需的所有引用,以获取用户数据。

最新更新