【问题标题】:I don't understand a completion handler and I need one in my code我不了解完成处理程序,我的代码中需要一个
【发布时间】:2020-03-28 06:17:57
【问题描述】:

我从 Firestore 捕获数据并分配给数组,其他问题中有人告诉我也许我需要一个完成处理程序,但我不明白如何工作,如果你帮助我编写代码并解释,这将非常有帮助这个概念对我来说。

class PetsTVC: UITableViewController {

    var db: Firestore!
    let uid = Auth.auth().currentUser?.uid
    var petslist = [String]()
    var pid = ""
    var pets = [paw]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.loadPets()
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }

    func loadPets(){
        let photo1 = UIImage(named: "Log")
        db = Firestore.firestore()

        db.collection("users").document(uid!).getDocument { documentSnapshot, error in
            guard let document = documentSnapshot else {
                return
            }
            self.petslist = document["petslist"] as? Array ?? [""]
        }
        for pet in self.petslist {
            self.db.collection("pets").document(pet).getDocument { documentSnapshot, error in
                guard let document = documentSnapshot else {
                    return
                }
                guard let data = document.data() else {
                    return
                }
                let pid = data["pid"] as! String? ?? ""
                let petName = data["petName"] as! String? ?? ""
                let infoPet = paw(id: pid, petName: petName, imagenMascota:photo1)
                self.pets.insert(infoPet, at: 0)
            }
        }
    }
}

如果您需要代码中的任何其他内容,请告诉我,因为我也是新来的,我不知道该在问题中提出什么。

【问题讨论】:

  • 不,对于这个答案,我问了这个问题。
  • 该代码无法运行,您也不需要完成处理程序。这只是众多选择之一。 Firestore 是异步的,并且在 firebase 闭包中返回的数据仅在该闭包中有效,并且需要一些时间才能从 Internet 返回。因此,在您的情况下,for pet 循环将在返回该数据之前执行 WAY。简单的解决方法是将 for 循环移动到 self.petslist = 之后的右侧,以便循环可以处理闭包内有效的数据。

标签: swift xcode firebase uitableview google-cloud-firestore


【解决方案1】:

完成处理程序是你交给某个函数的一段代码,它需要一点时间来完成它的工作,你不想在继续之前等待它完成。

就像您的邮箱距离很远,而您派一个孩子去取邮件。他们会花一分钟时间去邮箱,取邮件,然后回来,这样你就可以继续做其他事情了。由于孩子们很容易忘记,因此您可以在一张纸上写下一些说明,说明他们将邮件带回后如何处理。那张纸就是完成处理程序。那张纸上的说明很可能是将部分或全部邮件交给您,但可能包括首先过滤掉所有垃圾邮件或其他内容。

在您共享的代码中,您实际上有两个完成处理程序;在以db.collection("users").document(uid!).getDocument 开头的那一行和以self.db.collection("pets").document(pet).getDocument 开头的那一行{ } 内的所有内容都是完成处理程序(或闭包)。

为了将结果添加到您的表格中,您可能需要进行两项更改。第一个更改是将}self.petslist = document["petslist"] as? Array ?? [""] 行之后移到self.pets.insert(infoPet, at: 0) 行之后。

第二个变化是移动:

DispatchQueue.main.async {
    self.tableView.reloadData()
}

viewDidLoad()loadPets() 将它放在self.pets.insert(infoPet, at: 0) 行之后。这样,表格视图会在所有数据进入并被处理后重新加载。

现在你告诉你的孩子去拿邮件并把它放在一个篮子里,但是当孩子几乎没有从前门出来时试图把邮件从篮子里拿出来,你需要等到孩子回来,然后把邮件从篮子里拿出来。

【讨论】:

  • 感谢@theMikeSwan,这很有意义,而且效果很好,我更了解完成处理程序
  • @Camilo,很高兴它成功了。请随意勾选适合您的答案旁边的复选标记。
  • 澄清一下,Firebase 闭包中的 UI 调用是在主线程上执行的 - 所以 DispatchQueue.main.async 是无关的。另请注意,如果有很多宠物,在加载每个宠物后调用self.tableView.reloadData() 会导致闪烁。对于小型数据集,这很好。
【解决方案2】:

您不需要完成处理程序,您需要 DispatchGroup 在最后一次提取后重新加载表视图。

而且你必须将循环放入第一次数据库访问的完成处理程序中

override func viewDidLoad() {
    super.viewDidLoad()
    self.loadPets()
}

func loadPets(){
    let group = DispatchGroup()
    let photo1 = UIImage(named: "Log")
    db = Firestore.firestore()

    db.collection("users").document(uid!).getDocument { documentSnapshot, error in
        guard let document = documentSnapshot else { return }

        self.petslist = document["petslist"] as? [String] ?? []
        if self.petslist.isEmpty {
            DispatchQueue.main.async {
               self.tableView.reloadData()
            }
            return
        }

        for pet in self.petslist {
            group.enter()
            self.db.collection("pets").document(pet).getDocument { documentSnapshot, error in
                defer { group.leave() }

                guard let document = documentSnapshot,
                   let data = document.data() else { return }

                let pid = data["pid"] as? String ?? ""
                let petName = data["petName"] as? String ?? ""
                let infoPet = paw(id: pid, petName: petName, imagenMascota:photo1)
                self.pets.insert(infoPet, at: 0)
           }
        }
        group.notify(queue: .main)  {
           self.tableView.reloadData()
        }
    }
}

【讨论】:

  • 继续无效“'继续'只允许在循环内”
【解决方案3】:

让我们保持这个超级简单 - 可能不需要为此任务利用完成处理程序和调度分组。

问题在于:Firestore 是异步的,您只能在调用后的闭包内处理返回的数据。该循环之后的代码实际上将在循环中的代码之前执行,因为代码比互联网快。举个例子。

db.collection("users").document(uid!).getDocument... {
    //data is valid here
    print("Test")
}
print("Hello") //this code will execute and print Hello before Test is printed

所以这里有一个简单的解决方案来加载用户数据,然后获取他的宠物名称。我们有两个系列

users
   uid_0
      name: "Leroy"
      petslist:
         0: "pet_0"
         1: "pet_1"
         2: "pet_2"
pets
   pet_0
      petName: "Spot"
   pet_1
      petName: "Rover"
   pet_2
      petName: "Fluffy"

和代码

func loadPets() {
    let uid = "uid_1"
    self.db.collection("users2").document(uid).getDocument { documentSnapshot, error in
        guard let document = documentSnapshot else {
            return
        }
        let name = document["name"] as! String
        print("Owner \(name) has the following pets:")
        let petList = document["petslist"] as? Array ?? [""]
        for pet in petList {
            self.db.collection("pets").document(pet).getDocument { documentSnapshot, error in
                guard let document = documentSnapshot else {
                    return
                }
                let petName = document.get("petName") as? String ?? "No Pet Name"
                print(petName) //Or reload the tableview**
            }
        }
    }
}

和输出

Owner Leroy has the following pets:
Spot
Rover
Fluffy

话虽如此,您可以将闭包作为一种选择来做同样的事情,但我认为这超出了问题的范围。

**对于这个例子,我保持简单。实际上,如果你有很多数据,每次加载宠物后刷新tableView会导致闪烁,UI体验不好。这就是 DispatchGroup 可以发挥作用的地方,例如,您可以加载所有宠物,并在加载最后一个宠物时离开调度组并然后更新 tableView。

@Vadian 在他的回答中概述了一个 DispatchGroups 版本,但这是使用 DispatchGroup 加载宠物名称的变体:

func loadPets() {
    let uid = "uid_1"

    self.db.collection("users2").document(uid).getDocument { documentSnapshot, error in
        guard let document = documentSnapshot else { return }
        let name = document["name"] as! String
        print("Owner \(name) has the following pets:")

        let group = DispatchGroup()

        let petList = document["petslist"] as? Array ?? [""]

        for pet in petList {
            group.enter()
            self.db.collection("pets").document(pet).getDocument { documentSnapshot, error in
                guard let document = documentSnapshot else { return }
                let petName = document.get("petName") as? String ?? "No Pet Name"
                print(petName)
                group.leave()
            }
        }

        group.notify(queue: .main) {
            print("all pets loaded, reload tableview")
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-22
    • 1970-01-01
    • 2011-09-22
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多