簡體   English   中英

完成處理程序在 swift 中無法正常工作

[英]Completion Handler not working Properly in swift

我試圖用從 Firestore 數據庫獲得的數據填充兩個 arrays。 我成功地獲取了數據,但是已經很晚了,當我在 viewDidLoad 中打印它們時,它打印了空的 arrays。 所以我決定實現一個完成處理程序,但它仍然顯示和空數組。 誰能告訴我為什么我的打印語句在函數之前運行,即使我使用的是 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)
    }
}

我在 output 中得到了空的 arrays 盡管 arrays 應該在我使用 @escaping 調用打印之前填充。 請有人在這里幫助我

你實際上不是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管理。 在您的情況下,您調用在 function 本身中立即傳遞的閉包。 因此關閉不是 escaping。

firebase 數據庫也是異步的。 這意味着您不會立即收到結果

這部分:

{ (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)
        }

    }
}

本身是一個閉包,稍后將在產生查詢結果時執行。 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)

總結一下:firebase 查詢的代碼將在稍后調用(但您不知道何時),並且在定義 firebase 回調之后立即調用您的閉包“完成”,因此在它被調用之前。

您應該在 firebase 回調中調用完成的閉包,以便在填充 arrays 時擁有它。

我認為您的主要問題不是要填充您的arrays ,您的問題是如何讓它變得更好。

我舉了一個例子,說明如何以更好的方式做到這一點。

首先,將您的大function兩部分,然后將其從您的function中填充出來。

查看這段代碼並觀察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
        }
    }
}

如果您必須使用它調用主線程調用

DispathQueue.main.async {
   // code here
}

我希望它對你有所幫助。

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
        }
    }
}

}

自從我遇到這個問題以來,我已經有一段時間了,但我相信問題是你調用完成處理程序太晚了。 我的意思是,您可以嘗試在瀏覽完文檔后直接調用它。 一個想法可能是在完成時返回它,或者像你一樣做。 試試這個:

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()
    }

這將做的是,當您調用 @escaping 類型的完成塊並在它之后返回時,您將不再響應該完全塊,因此只會使用接收到的數據,因此不關心 function了。

根據我的說法,我的好習慣是在完成塊中返回 object 並使用單獨的函數以更容易調試和更高效,它是否允許您使用 @escaping 返回並在返回之后返回。

您可以使用我展示的單獨方法來組合這兩種方法並更新 UI。 如果您要更新 UI,請記住使用以下命令獲取主隊列:

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

那應該行得通。 偉大的問題,希望它有所幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM