【问题标题】:Async Future Promise not working in Swift 5异步未来承诺在 Swift 5 中不起作用
【发布时间】:2021-10-03 20:26:11
【问题描述】:

我正在尝试实现对函数 loadUserFromFirebase 的异步调用,并根据用户属性填充并返回一个数组,然后我使用该属性将其写入 firebase 集合。

我正在尝试使用来自 Swift 的 Combine 框架,使用 future 和 Promise,但由于某种原因,receiveCompletion 和 receiveValue 没有被调用,因此我从我的异步函数调用中得到一个空数组。

这是我的代码:

var cancellable = Set<AnyCancellable>()
    func loadUserFromFirebase(groupUserIds: [String], handler: @escaping ([groupMate]) -> Void) {
        
        var groupmateID = 0
        var groupMates : [groupMate] = []
        print("groupUserIds: \(groupUserIds)" )
        
        for groupUserUID in groupUserIds {
            print("groupUserUID: \(groupUserUID)" )
            
            let future = Future<groupMate, Never> { promise in
                self.ref.collection("Users").document(groupUserUID).getDocument(){
                    
                    (friendDocument, err) in
                
                    if let err = err {
                            print("Error getting documents \(err)")
                    } else {
                      //  print("friendDocument: \(String(describing: friendDocument?.data()))" )

                        let groupUsername = (friendDocument?.data()?["username"]) as! String
                        let groupUID = (friendDocument?.data()?["uid"]) as! String
                        let groupName = (friendDocument?.data()?["name"]) as! String
                        let groupPic = (friendDocument?.data()?["imageurl"]) as! String

                        promise(.success(groupMate(id: groupmateID, uid: groupUID , name: groupName , username: groupUsername, pic: groupPic)))
                    }
                    
                    groupmateID += 1
                }
            }
            print("in receiveCompletion")

            future.sink(receiveCompletion: { completion in
                print("in receiveCompletion")
                print(1, completion)
                switch completion {
                    case .failure(let error):
                        print(3, error)
                        handler([])
                        return
                    case .finished:
                        break
                }
            },
            receiveValue: {
                print("in receiveValue")
                groupMates.append($0)
                print(groupMates)
                handler(groupMates)
            }).store(in: &cancellable)
        }
    }
    
    
    func creategroup(groupName: String){
        addedTogroupUsers.append(self.uid)
        
        print("here111")
        loadUserFromFirebase(groupUserIds: addedTogroupUsers) { groupMates in
            print("here222")

            let groupData: [String: Any] = [
                "groupName": "\(groupName)",
                "groupmates": groupMates
            ]
            print("here333 \(groupData)")

            print("groupMates are \(self.groupMates)")
            
            
            var groupref: DocumentReference? = nil
                groupref = self.ref.collection("groups").addDocument(data: groupData) { err in
                if let err = err {
                    print("Error adding document: \(err)")
                } else {
                    print("Document added with ID: \(groupref!.documentID)")
                    for addedgroupUser in self.addedTogroupUsers {
                        self.ref.collection("Users").document(addedgroupUser).updateData([
                                "groups": FieldValue.arrayUnion([groupref!.documentID])
                            ])
                    }
                }
            }
                print("groupName is \(groupName) and addedTogroup are \(self.addedTogroupUsers)")
        }
    }

我正在尝试查看 AnyCancellable 是否可行,但由于我使用的是未来承诺的链式数组,因此我不确定如何实现它。请让我知道您将如何解决此问题,以便数组确实被填充,因为文档确实存在并且方法调用中的打印工作但 createGroup 函数中的 groupMates 数组随后会打印一个空数组。谢谢!

编辑:按照建议将 AnyCancallable 与完成处理程序一起添加到代码中

【问题讨论】:

    标签: swift firebase asynchronous combine


    【解决方案1】:

    处理异步函数可能很棘手。你得到一个空数组,因为你在 loadUserFromFirebase 中返回得太早了。使用旧式闭包尝试这种方法(未经测试):

    func loadUserFromFirebase(groupUserIds: [String], handler: @escaping ([groupMate]) -> Void) { // <-- here
            var groupmateID = 0
            var groupMates : [String] = []
            print("groupUserIds: \(groupUserIds)" )
            
            for groupUserUID in groupUserIds {
                print("groupUserUID: \(groupUserUID)" )
                
                let future = Future<groupMate, Never> { promise in
                    self.ref.collection("Users").document(groupUserUID).getDocument(){ (friendDocument, err) in
                        if let err = err {
                                print("Error getting documents \(err)")
                        } else {
                            print("friendDocument: \(String(describing: friendDocument?.data()))" )
                            
                            let groupUsername = (friendDocument?.data()?["username"]) as! String
                            let groupUID = (friendDocument?.data()?["uid"]) as! String
                            let groupName = (friendDocument?.data()?["name"]) as! String
                            let groupPic = (friendDocument?.data()?["imageurl"]) as! String
                            
                            promise(.success(groupMate(id: groupmateID, uid: groupUID , name: groupName , username: groupUsername, pic: groupPic)))
                        }
                        groupmateID += 1
                    }
                }
                
                future.sink(receiveCompletion: { completion in
                    print("in receiveCompletion")
                    print(1, completion)
                    switch completion {
                    case .failure(let error):
                        print(3, error)
                        handler([])  // <-- here
                        return          // <-- here
                    case .finished:
                        break
                    }
                },
                receiveValue: {
                    print("in receiveValue")
                    groupMates.append($0)
                    print(groupMates)
                    handler(groupMates)  // <-- here
                })
            }
            // <-- do not return here
        }
    
       
        func creategroup(groupName: String) {
            addedTogroupUsers.append(self.uid)
            
            // -- here wait until you get the data
            loadUserFromFirebase(groupUserIds: addedTogroupUsers) { groupMates in
                
                let groupData: [String: Any] = [
                    "groupName": "\(groupName)",
                    "groupmates": groupMates      // <-- here
                ]
                
                print("groupMates are \(self.groupMates)")
                
                var groupref: DocumentReference? = nil
                groupref = ref.collection("groups").addDocument(data: groupData) { err in
                    if let err = err {
                        print("Error adding document: \(err)")
                    } else {
                        print("Document added with ID: \(groupref!.documentID)")
                        for addedgroupUser in self.addedTogroupUsers {
                            self.ref.collection("Users").document(addedgroupUser).updateData([
                                "groups": FieldValue.arrayUnion([groupref!.documentID])
                            ])
                        }
                    }
                }
                print("groupName is \(groupName) and addedTogroup are \(addedTogroupUsers)")
            }
            
        }
    

    注意,如果您的目标是 ios 15、macos 12,那么使用 swift 5.5 的 async/await/task 功能会获得更好的服务。它们真的很好用。

    编辑:尝试返回所有结果

    func loadUserFromFirebase(groupUserIds: [String], handler: @escaping ([groupMate]) -> Void) {
        var groupmateID = 0
        var groupMates : [String] = []
        print("groupUserIds: \(groupUserIds)" )
        
        var arr: [Future<groupMate, Never>] = [Future<groupMate, Never>]()
        
        var cancellable = Set<AnyCancellable>()
        
        for groupUserUID in groupUserIds {
            print("groupUserUID: \(groupUserUID)" )
            
            let future = Future<groupMate, Never> { promise in
                self.ref.collection("Users").document(groupUserUID).getDocument(){ (friendDocument, err) in
                    if let err = err {
                        print("Error getting documents \(err)")
                    } else {
                        print("friendDocument: \(String(describing: friendDocument?.data()))" )
                        
                        let groupUsername = (friendDocument?.data()?["username"]) as! String
                        let groupUID = (friendDocument?.data()?["uid"]) as! String
                        let groupName = (friendDocument?.data()?["name"]) as! String
                        let groupPic = (friendDocument?.data()?["imageurl"]) as! String
                        
                        promise(.success(groupMate(id: groupmateID, uid: groupUID , name: groupName , username: groupUsername, pic: groupPic)))
                    }
                    groupmateID += 1
                }
            }
            arr.append(future)
        }
        
        Publishers.MergeMany(arr)
            .collect()
            .sink { _ in
                print("-----> merging ")
            } receiveValue: { value in
                print("-----> value: \(value)")
                groupMates = value  // <--- maybe append?
                print(groupMates)
                handler(groupMates)
            }
            .store(in: &cancellable)
    }
    

    【讨论】:

    • 嗨@workingdog 非常感谢您的回复!不幸的是,我的目标是 iOS 13+,所以我不能使用 swift 5.5 功能。我尝试更新它以使用处理程序,但它似乎卡在 loadUserFromFirebase 调用中,并且我尝试在创建组 loadUserFromFirebase 调用之后立即打印,该调用从未被击中。试图找出问题可能出在哪里
    • 它似乎永远不会去receiveCompletion或receiveValue。我想知道我是否错误地调用了 future.sink 或其他东西。它还通过 groupUserIds 循环,因此它确实有多个承诺。我肯定非常卡住,并会感谢任何帮助。再次感谢!
    • 我已经忘记了这一切是如何与 Futures 和 Promise 一起工作的,但你不必有这样的东西: var cancellable = Set() // 在函数 future.sink 之外(receiveCompletion: { completion in ... }).store(in: &cancellable)
    • 非常感谢!这几乎成功了,我只有最后一个问题。似乎承诺只接受循环中的几个用户中的一个。在我的组队友数组中,我希望它存储所有用户。我应该在 promise .success 条件中进行哪些更改,以确保将所有用户添加到数组中,而不仅仅是第一次成功。
    • 有一个“Publishers.MergeMany(f)”和“.collect()”。不知道如何使用它们,但我怀疑你拥有的 for 循环不适合它。您可能需要重新考虑没有循环的未来结构。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    • 1970-01-01
    • 2017-01-22
    • 2018-06-14
    相关资源
    最近更新 更多