简体   繁体   English

完成处理程序在 swift 中无法正常工作

[英]Completion Handler not working Properly in swift

im trying to populate two arrays with the data i get from the firestore database.我试图用从 Firestore 数据库获得的数据填充两个 arrays。 im getting the data successfully however it was late and when i printed them in viewDidLoad it printed empty arrays.我成功地获取了数据,但是已经很晚了,当我在 viewDidLoad 中打印它们时,它打印了空的 arrays。 so i decided to implement a completion handler however it still shows and empty array.所以我决定实现一个完成处理程序,但它仍然显示和空数组。 can anyone tell me why my print statement runs before the functions even though im using escaping谁能告诉我为什么我的打印语句在函数之前运行,即使我使用的是 escaping

func yourFunctionName(finished: @escaping () -> Void) {
    db.collection("countries")
        .whereField("capital", isEqualTo: "washington")
        .getDocuments { (snapshot, error) in
        if error == nil{
            for document in snapshot!.documents {
                let documentData = document.data()

                //print(document.documentID)
                //print(documentData)

                self.countries.append(document.documentID)
            }

        }
    }

    db.collection("countries")
        .whereField("climate", isEqualTo: "pleasant")
        .getDocuments { (snapshot, error) in
        if error == nil {
            for document in snapshot!.documents{
                let documentData = document.data()

                //print(document.documentID)
                //print(documentData)

                self.countries2.append(document.documentID)
            }
        }
    }
    finished()

}

viewDidLoad(){
    yourFunctionName {
        print(self.countries)
        print(self.countries2)
    }
}

i get the empty arrays in the output although the arrays should have been filled before i called print though im using @escaping.我在 output 中得到了空的 arrays 尽管 arrays 应该在我使用 @escaping 调用打印之前填充。 please someone help me here请有人在这里帮助我

You are actually not escaping the closure.你实际上不是escaping 的关闭。 For what I know the "@escaping" is a tag that the developper of a function use to signify the person using the function that the closure he/she is passing will be stored and call later (after the function ends) for asynchronicity and memory management. For what I know the "@escaping" is a tag that the developper of a function use to signify the person using the function that the closure he/she is passing will be stored and call later (after the function ends) for asynchronicity and memory管理。 In your case you call the closure passed immediately in the function itself.在您的情况下,您调用在 function 本身中立即传递的闭包。 Hence the closure is not escaping.因此关闭不是 escaping。

Also the firebase database is asynchronous. firebase 数据库也是异步的。 Meaning that you don't receive the result immediately这意味着您不会立即收到结果

This part:这部分:

{ (snapshot, error) in
    if error == nil{
        for document in snapshot!.documents {
            let documentData = document.data()

            //print(document.documentID)
            //print(documentData)

            self.countries.append(document.documentID)
        }

    }
}

is itself a closure, that will be executed later when the result of the query is produced.本身是一个闭包,稍后将在产生查询结果时执行。 As you can see in the doc, the function is escaping the closure: https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/Query.html#getdocumentssource:completion : As you can see in the doc, the function is escaping the closure: https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/Query.html#getdocumentssource:completion :

func getDocuments(source: FirestoreSource, completion: @escaping FIRQuerySnapshotBlock)

So to summarise: The code for the firebase query will be call later (but you don't know when), and your closure "finished" is called immediately after having define the firebase callback, thus before it has been called.总结一下:firebase 查询的代码将在稍后调用(但您不知道何时),并且在定义 firebase 回调之后立即调用您的闭包“完成”,因此在它被调用之前。

You should call your finished closure inside the firebase callback to have it when the arrays are populated.您应该在 firebase 回调中调用完成的闭包,以便在填充 arrays 时拥有它。

I think your main problem here is not about to populate your arrays , your problem is how to get it better.我认为您的主要问题不是要填充您的arrays ,您的问题是如何让它变得更好。

I did an example of how you could do that in a better way.我举了一个例子,说明如何以更好的方式做到这一点。

First, break your big function in two, and populate it out of your function .首先,将您的大function两部分,然后将其从您的function中填充出来。

Look at this code and observe the viewDidLoad implementation.查看这段代码并观察viewDidLoad实现。

func countries(withCapital capital: String, completionHandler: (Result<Int, Error>) -> Void) {
        db.collection("countries")
        .whereField("capital", isEqualTo: capital)
        .getDocuments { (snapshot, error) in
            guard error == nil else {
                completionHandler(.failure(error!))
                return

            }

            let documents = snapshot!.documents
            let ids = documents.map { $0.documentID }
            completionHandler(.success(ids))
    }

}

func countries(withClimate climate: String, completionHandler: (Result<Int, Error>) -> Void) {
        db.collection("countries")
        .whereField("climate", isEqualTo: climate)
        .getDocuments { (snapshot, error) in
            guard error == nil else {
                completionHandler(.failure(error!))
                return

            }

            let documents = snapshot!.documents
            let ids = documents.map { $0.documentID }
            completionHandler(.success(ids))
    }
}


func viewDidLoad(){
    countries(withClimate: "pleasant") { (result) in
        switch result {
        case .success(let countries):
            print(countries)
            self.countries2 = countries
        default:
            break
        }
    }

    countries(withCapital: "washington") { (result) in
        switch result {
        case .success(let countries):
            print(countries)
            self.countries = countries
        default:
            break
        }
    }
}

If you have to call on main thread call using it如果您必须使用它调用主线程调用

DispathQueue.main.async {
   // code here
}

I hope it helped you.我希望它对你有所帮助。

They are returning empty arrays because Firebase's function is actually asynchronous (meaning it can run after the function "yourFunctionName" has done its work) in order for it to work as intended (print the filled arrays) All you need to do is call it inside Firebase's closure itself, like so: They are returning empty arrays because Firebase's function is actually asynchronous (meaning it can run after the function "yourFunctionName" has done its work) in order for it to work as intended (print the filled arrays) All you need to do is call it inside Firebase 的关闭本身,如下所示:

func yourFunctionName(finished: @escaping () -> Void) {
db.collection("countries")
    .whereField("capital", isEqualTo: "washington")
    .getDocuments { (snapshot, error) in
    if error == nil{
        for document in snapshot!.documents {
            let documentData = document.data()
            self.countries.append(document.documentID)
            finished() //<<< here
        }

    }
}

db.collection("countries")
    .whereField("climate", isEqualTo: "pleasant")
    .getDocuments { (snapshot, error) in
    if error == nil {
        for document in snapshot!.documents{
            let documentData = document.data()
            self.countries2.append(document.documentID)
            finished() //<<< and here
        }
    }
}

} }

I has been sometime since I encountered that problem but I beleve the issue is that you are calling the completion handler to late.自从我遇到这个问题以来,我已经有一段时间了,但我相信问题是你调用完成处理程序太晚了。 What I mean is that you can try to call it directly after you have lopped throught the documents.我的意思是,您可以尝试在浏览完文档后直接调用它。 One idea could be to return it in the compltion or just do as you do.一个想法可能是在完成时返回它,或者像你一样做。 Try this instead:试试这个:

func yourFunctionName(finished: @escaping ([YourDataType]?) -> Void) {
        var countires: [Your Data Type] = []
        db.collection("countries")
            .whereField("capital", isEqualTo: "washington")
            .getDocuments { (snapshot, error) in
            if error == nil{
                for document in snapshot!.documents {
                    let documentData = document.data()
                    //print(document.documentID)
                    //print(documentData)
                    countries.append(document.documentID)
                }
               finished(countries)
               return
            }
        }
    }

    func yourSecondName(finished: @escaping([YouDataType]?) -> Void) {
    var countries: [Your data type] = []

    db.collection("countries")
            .whereField("climate", isEqualTo: "pleasant")
            .getDocuments { (snapshot, error) in
            if error == nil {

                for document in snapshot!.documents{
                    let documentData = document.data()
                    //print(document.documentID)
                    //print(documentData)
                   countires.append(document.documentID)
                }
              finished(countires)
              return
            }
        }

    func load() {
        yourFunctionName() { countries in
           print(countires)
        }
        yourSecondName() { countries in
           print(countries)
        }
   }

   viewDidLoad(){
        load()
    }

What this will do is that when you call the completion block that is of type @escaping as well as returning after it you won't respond to that completely block any longer and therefore will just use the data received and therefore not care about that function anymore.这将做的是,当您调用 @escaping 类型的完成块并在它之后返回时,您将不再响应该完全块,因此只会使用接收到的数据,因此不关心 function了。

I good practice according to me, is to return the object in the completion block and use separate functions to be easier to debug and more efficient as well does it let you return using @escaping and after that return.根据我的说法,我的好习惯是在完成块中返回 object 并使用单独的函数以更容易调试和更高效,它是否允许您使用 @escaping 返回并在返回之后返回。

You can use a separate method as I showed to combine both methods and to update the UI.您可以使用我展示的单独方法来组合这两种方法并更新 UI。 If you are going to update the UI remember to fetch the main queue using:如果您要更新 UI,请记住使用以下命令获取主队列:

DispathQueue.main.async {
    // Update the UI here
}

That should work.那应该行得通。 Greate question and hope it helps!伟大的问题,希望它有所帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM