I am trying to get documents from a FireStore database. I need these documents to be loaded before moving forward in my function. Here is the code for reference:
The view controller calling the FireStore service functions:
let service = FirestoreServices()
service.get(cottage: "test123456789") { model in
nextViewController.cottageModel = model!
self.present(nextViewController, animated:true, completion:nil)
}
The FireStore service method being called:
func get(cottage: String, completionHandler: @escaping (CottageTrip?) -> ()) {
//get a reference to the firestore
let db = Firestore.firestore()
//get the references to the collections
let cottageRef = db.collection("cottages").document(cottage)
//get the fields from the initial document and load them into the cottage model
cottageRef.getDocument { (document, error) in
if let document = document, document.exists {
//create the cottage model to return
let cottageModel: CottageTrip = CottageTrip()
//get the subcollections of this document
let attendeesCollection = cottageRef.collection("attendees")
//other collections
//here I get all info from initial document and store it in the model
let group = DispatchGroup()
print("here")
group.enter()
//get the attendees
DispatchQueue.global(qos: .userInitiated).async {
attendeesCollection.getDocuments() { (querySnapshot, err) in
print("here2")
if let err = err {
print("Error getting documents: \(err)")
} else {
//get data
}
group.leave()
}
}
print("after async call")
//wait here until the attendees list is built
group.wait()
print("after wait")
//create the cars
carsCollection.getDocuments() { (querySnapshot, err) in
print("in car collection get doc call")
if let err = err {
print("Error getting documents: \(err)")
} else {
//get car data
}
}
}
//this is where she should block until all previous get document operations complete
completionHandler(cottageModel)
} else {
print("Document does not exist")
completionHandler(nil)
}
}
}
I am realizing that the print("here2")
never prints so it seems like it blocks on the group.wait()
. I need to use group.wait()
rather than a notify because I need this function to access subcollections and documents only after the attendees collection is loaded as I need these values for the subcollections and documents. I have read a lot of answers online and most people use group.wait()
in this scenario but for some reason I can not get it to work for me without locking and freezing the application.
As algrid pointed out, you have a deadlock because you are waiting on the main thread, which Firestore needs to call its closures.
As a general rule, avoid calling wait
and you will not deadlock. Use notify
, and just call your closure inside that notify
closure.
So, for example, assuming that you do not need the results from attendees
in order to query the cars
, you can just use a notify
dispatch group pattern, eg
func get(cottage: String, completionHandler: @escaping (CottageTrip?) -> Void) {
let db = Firestore.firestore()
let cottageRef = db.collection("cottages").document(cottage)
cottageRef.getDocument { document, error in
guard let document = document, document.exists else {
print("Document does not exist")
completionHandler(nil)
return
}
let cottageModel = CottageTrip()
let attendeesCollection = cottageRef.collection("attendees")
let carsCollection = cottageRef.collection("cars")
let group = DispatchGroup()
group.enter()
attendeesCollection.getDocuments() { querySnapshot, err in
defer { group.leave() }
...
}
group.enter()
carsCollection.getDocuments() { querySnapshot, err in
defer { group.leave() }
...
}
group.notify(queue: .main) {
completionHandler(cottageModel)
}
}
}
Also, as an aside, but you do not have to dispatch anything to a global queue, as these methods are already asynchronous.
If you needed the result from one in order to initiate the next, you can just nest them. This will be slower (because you magnify the network latency effect), but also eliminates the need for the group at all:
func get(cottage: String, completionHandler: @escaping (CottageTrip?) -> Void) {
let db = Firestore.firestore()
let cottageRef = db.collection("cottages").document(cottage)
cottageRef.getDocument { document, error in
guard let document = document, document.exists else {
print("Document does not exist")
completionHandler(nil)
return
}
let cottageModel = CottageTrip()
let attendeesCollection = cottageRef.collection("attendees")
let carsCollection = cottageRef.collection("cars")
attendeesCollection.getDocuments() { querySnapshot, err in
...
carsCollection.getDocuments() { querySnapshot, err in
...
completionHandler(cottageModel)
}
}
}
}
Either way, I might be inclined to break this up into separate functions, as it is a little hairy, but the idea would be the same.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.