问题
I capture the data from Firestore and assign to array, someone in other question told me maybe I need a completion handler but I don't understand how to work, it would be very helpful if you help me with the code and explain this concept to me.
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)
}
}
}
}
If you need anything else from the code just tell me, because I'm also new here and I don't know what to put in the questions.
回答1:
A completion handler is a bit of code you hand to some function that is going to take a moment to do its job and you don't want to have to wait for it to finish before moving on.
Like if your mailbox was a ways away and you sent a kid off to get the mail. It is going to take them a minute to go to the mailbox, get the mail, and come back so you are going to carry on with other things. Since kids forget easily you write some instructions on a piece of paper for what to do with the mail once they bring it back. That piece of paper is the completion handler. Most likely one of the instructions on that piece of paper is to hand some or all of the mail to you but maybe it includes filtering out all the junk mail first or something.
In the code you have shared you actually have two completion handlers; on the line that starts db.collection("users").document(uid!).getDocument and the one that starts self.db.collection("pets").document(pet).getDocument everything that is inside the { } is the completion handler (or closure).
In order to get results into your table you likely need to make two changes. The first change is to move the } from after the line self.petslist = document["petslist"] as? Array ?? [""] so that is after the line self.pets.insert(infoPet, at: 0).
The second change is to move:
DispatchQueue.main.async {
self.tableView.reloadData()
}
Out of viewDidLoad() and into loadPets() placing it after the line self.pets.insert(infoPet, at: 0). That way the table view reloads after all the data has come in and been processed.
Right now you are telling your kid to go get the mail and put it in a basket but trying to get the mail out of the basket when the kid has barely made it out the front door, you need to wait until the kid gets back, then grab the mail out of the basket.
回答2:
You don't need a completion handler, you need DispatchGroup to reload the table view after the last fetch.
And you have to put the loop into the completion handler of the first database access
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:
Let's keep this super simple - leveraging completion handlers and dispatch groupds for this task may not be needed.
Here's the problem: Firestore is asynchronous and you can only work with the returned data within the closure following the call. Code after that loop will actually execute before the code in the loop because code is faster than the internet. So for example.
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
So here's a straightforward solution to load a users data and then fetch his pets name. We have two collection
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"
and the code
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**
}
}
}
}
and the output
Owner Leroy has the following pets:
Spot
Rover
Fluffy
That being said, you can incorporate closures as an option to do the same thing but I think that goes beyond the scope of the question.
**for this example, I'm keeping it easy. In reality if you've got a lot of data, refreshing the tableView after each pet is loaded will cause flicker and not a good UI experience. This is where a DispatchGroup can come into play as, for example, you can load all of the pets and when when the last pet is loaded leave the dispatch group and then update the tableView.
@Vadian has a version of DispatchGroups outlined in his answer but here's a variation using a DispatchGroup for loading the pet names:
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")
}
}
}
来源:https://stackoverflow.com/questions/59159190/i-dont-understand-a-completion-handler-and-i-need-one-in-my-code