swift for循环数据顺序不正确

Posted

技术标签:

【中文标题】swift for循环数据顺序不正确【英文标题】:swift for loop order of data is not right 【发布时间】:2021-05-09 07:14:23 【问题描述】:

我想从 firebase 获取数据并将它们放入一个数组中。函数的第一部分总是按正确的顺序排列,当我打印时我可以看到它(调试(文件)。但是在 for 循环之后,文档的顺序混乱,我总是得到随机顺序。我不应该总是得到相同的顺序?

func getUnreadMessages()

    guard let uid = AuthViewModel.shared.userSession?.uid else return
    Firestore.firestore().collection("users").document(uid).collection("chats").order(by: "created", descending: true).getDocuments  (snapshot, _) in
        guard let files = snapshot?.documents.compactMap( $0.documentID ) else return
        print("DEBUG: \(files)")
        
        for file in files
            Firestore.firestore().collection("users").document(uid).collection("chats").document(file).collection("messages").whereField("read", isEqualTo: false).getDocuments  (snapshot, _) in
                guard let documents = snapshot?.documents.compactMap( $0.documentID ) else return
                print("DEBUG: \(documents)")
                self.count.append(documents.count)
                print("DEBUG: \(self.count)")
            
        
    

【问题讨论】:

您正在进行异步调用,因此无法保证您收到回复的顺序。 “我不应该总是得到相同的订单吗?”不,你不应该编写依赖于顺序的代码。 该代码一遍又一遍地读取同一个集合。例如它读取.collection("users").document(uid).collection("chats"),这意味着集合文档在代码中可用。然后它在一个紧密的循环中再次读取同一个集合;没有理由这样做。读取集合后,可以迭代该集合(快照)中的文档。似乎尝试的任务是获取文档计数?如果是这样,函数名称不应该是getUnreadMessages,因为这不是它正在做的事情。能否澄清一下问题和代码? 【参考方案1】:

您会得到不同的结果顺序,因为当您以正确的顺序调用数据库时,不能保证数据库会以相同的顺序返回您的调用,因为某些调用比其他调用花费的时间更长。我认为最简单的解决方案是记录原始顺序,将其附加到您的第二次调用中的数据(您确定文档计数的位置),然后按原始顺序对集合(数组)进行排序。

将此索引值附加到文档计数的最简单方法是自定义模型:

struct MessageCount 
    let count: Int // this is the message count you're after
    let n: Int // this is the index of the original order
    
    init(count: Int, n: Int) 
        self.count = count
        self.n = n
    

然后只需使用调度组来协调异步任务,并在调度组完成后,按index 对数组进行排序,您将获得按预期顺序排列的消息计数数组:

func getUnreadMessages() 
    guard let uid = AuthViewModel.shared.userSession?.uid else 
        return
    
    let db = Firestore.firestore() // instantiate it once since it could be created hundreds or thousands of times in this function
    
    db.collection("users").document(uid).collection("chats").order(by: "created", descending: true).getDocuments  (snapshot, error) in
        guard let snapshot = snapshot,
              !snapshot.isEmpty else 
            if let error = error 
                print(error) // you oddly omitted the error in your code, never do that
            
            return
        
        let dispatch = DispatchGroup() // set up the dispatch group outside the loop
        var messageCounts = [MessageCount]() // this temp array will carry the data with the index
        
        // to record the original order of the loop, just enumerate it and access `n` (the index)
        for (n, doc) in snapshot.documents.enumerated() 
            dispatch.enter() // enter dispatch on each iteration
            
            db.collection("users").document(uid).collection("chats").document(doc.documentID).collection("messages").whereField("read", isEqualTo: false).getDocuments  (snapshot, error) in
                if let snapshot = snapshot 
                    let c = snapshot.count // get the message count
                    let count = MessageCount(count: c, n: n) // add it to the model along with n which is captured by the parent closure
                    
                    messageCounts.append(count) // append to our temp array
                 else if let error = error 
                    print(error)
                
                
                dispatch.leave() // leave dispatch no matter the outcome
            
        
        
        // this is the completion handler of the dispatch group
        dispatch.notify(queue: .main) 
            // sort the array by index and then map it to just get the message counts
            let counts = messageCounts.sorted(by:  $0.n < $1.n ).map( $0.count )
        
    

【讨论】:

【参考方案2】:

返回结果的顺序由order(by 子句确定。否则结果可能看起来有些随机。

在这种情况下,第一个 Firebase 调用指定了一个顺序,因此这些文档将始终以正确的顺序返回。

collection("chats").order(by: "created"

但是下一个 firebase 调用没有指定顺序,所以返回的文档可能有点不一致。

.collection("messages").whereField

我们需要有一些方法来保证这个顺序。

假设结构是这样的

chats (collection)
   user ids (documents)
      chats (collection)
         chat ids (documents)
            messages (collection)
               message ids (documents that you want ordered)

消息 ID 需要有一个字段来排序它们 - 调用 ordering

这是打印每个聊天 id 中消息数量的计数然后按顺序打印消息的代码

func getUnreadMessages() 
    let uid = "uid_0"
    self.db.collection("users_chats").document(uid).collection("chats").getDocuments(completion:  snapshot, error in
        if let err = error 
            print(err.localizedDescription)
            return
        

        guard let docs = snapshot?.documents else  return 

        for doc in docs 
            let ref = doc.reference.collection("messages")
            ref.order(by: "ordering").getDocuments(completion:  messagesSnapshot, error in
                if let err = error 
                    print(err.localizedDescription)
                    return
                

                guard let messages = messagesSnapshot?.documents else  return 

                print("the chat document: \(doc.documentID) has \(messages.count) messages")
                for msg in messages 
                    let order = msg.get("ordering")
                    let msg = msg.get("read")
                    print("order: \(order!)", "is read: \(msg!)")
                
            )
        
    )

如果聊天 0 中有 3 条消息,输出如下所示

the chat document: chat_0 has 3 messages
the chat document: chat_1 has 0 messages
the chat document: chat_2 has 0 messages
order: 0 isRead: 0
order: 1 isRead: 1
order: 2 isRead: 0

【讨论】:

以上是关于swift for循环数据顺序不正确的主要内容,如果未能解决你的问题,请参考以下文章

Swift学习笔记之---for循环与while循环

Swift学习笔记之---for循环与while循环

Swift学习笔记之---for循环与while循环

使用 DispatchGroup、DispatchQueue 和 DispatchSemaphore 按顺序执行带有 for 循环的 Swift 4 异步调用

为啥这个for循环不执行?

两个for循环执行顺序