I have a method to retrieve data. This data can but doesn't have to contain an image. The method below is retrieving the data in order which is crucial for the app:
static func getWishes(dataSourceArray: [Wishlist], completion: @escaping (_ success: Bool, _ dataArray: [Wishlist]) -> Void){
var dataSourceArrayWithWishes = dataSourceArray
let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
var j = 0
let dispatchSemaphore = DispatchSemaphore(value: 0)
let dispatchSemaphoreOuter = DispatchSemaphore(value: 0)
let dispatchQueue1 = DispatchQueue(label: "taskQueue")
var listCounter = 0
dispatchQueue1.async {
for list in dataSourceArray {
listCounter += 1
db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").order(by: "wishCounter").getDocuments() { ( querySnapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
// dispatch group to make sure completion only fires when for loop is finished
// append every Wish to array at wishIDX
var i = 0
let dispatchQueue = DispatchQueue(label: "taskQueue1")
if (querySnapshot?.documents.count)! > 0 {
dispatchQueue.async {
for document in querySnapshot!.documents {
let documentData = document.data()
let imageUrlString = document["imageUrl"] as? String ?? ""
if let imageUrl = URL(string: imageUrlString) {
KingfisherManager.shared.retrieveImage(with: imageUrl, options: nil, progressBlock: nil, completionHandler: { result in
var image = UIImage()
switch result {
case .success(let abc):
image = abc.image
case .failure(let error):
print(error)
break
}
dataSourceArrayWithWishes[wishIDX].wishes.append(Wish(image: image))
i = i + 1
if i == querySnapshot?.documents.count {
j = j + 1
dispatchSemaphoreOuter.signal()
dispatchSemaphore.signal()
if j == dataSourceArray.count {
print("completion1")
completion(true, dataSourceArrayWithWishes)
}
} else {
dispatchSemaphore.signal()
}
})
} else {
dataSourceArrayWithWishes[wishIDX].wishes.append(Wish(image: image))
i = i + 1
if i == querySnapshot?.documents.count {
j = j + 1
dispatchSemaphoreOuter.signal()
dispatchSemaphore.signal()
if j == dataSourceArray.count {
print("completion3")
completion(true, dataSourceArrayWithWishes)
}
} else {
dispatchSemaphore.signal()
}
}
dispatchSemaphore.wait()
}
}
} else {
j = j + 1
dispatchSemaphoreOuter.signal()
}
}
}
// without this code the function will not fire a completion if only a single empty list is in dataSourceArray
// if listCounter == dataSourceArray.count {
// print("completion")
// completion(true, dataSourceArrayWithWishes)
// }
// continue
}
}
}
This method works fine if if (querySnapshot?.documents.count)! > 0
is true
but if a dataSourceArray
only contains an empty list
it fails because it never fires the completion
. I tried fixing that with a listCounter
and with the code at the bottom. This sort of works but is not clean because that always fires 2 completions
which looks weird in the app. Anyone have an idea on how I could fix the issue if there is only an empty list
in dataSourceArray
?
I am really stuck here so I am happy for every help!
You forgot to call your completion handler in the else
branch of your if (querySnapshot?.documents.count)! > 0
Note: As other commenters already pointed out it would be good to split your code into multiple functions for better readability and maintainability.
You can pass on your completion handler to these functions as parameter.