Reputation: 445
I hope you are doing fine. I am trying to achieve following thing:
Currently, I have following approach
self.dataAccessService.fetchRepliesByCommentId(completionHandler: { (commentReplyArray) in
for var i in 0..<commentReplyArray.count {
let commentReply = commentReplyArray[i]
let commentItem = CommentItem()
self.fetchDetailsAboutCommentReply(commentReplyObject: commentReply) { (commentItem) in
commentItem.commentObject = commentReply
dataSource.insert(commentItem, at: index + i + 1) -> APP CRASHES HERE, i is never 0 here
ips.append(IndexPath(row: index + i + 1 , section: 0))
if (i == commentReplyArray.count - 1) {
self.delegate?.didLoadReplies(dataSource: dataSource, ips: ips)
}
}
}
}, commentId: commentItem.commentObject.id)
My fetchDetailsAboutCommentReply function:
private func fetchDetailsAboutCommentReply(commentReplyObject:CommentReply, completionHandler:@escaping(CommentItem)->()) {
let group = DispatchGroup()
let commentItem = CommentItem()
group.enter()
self.dataAccessService.fetchUserById(completionHandler: { (userObject) in
commentItem.userObject = userObject
group.leave()
}, uid: commentReplyObject.userId)
group.enter()
self.dataAccessService.fetchDownloadURLOfProfileImage(organizerId: commentReplyObject.userId) { (contentURL) in
commentItem.userObject.contentURL = contentURL
group.leave()
}
group.notify(queue: .main) {
completionHandler(commentItem)
}
}
My question is how, I can change my code, so the loop basically "pauses" until I fetch every detail information of the iterated object, add it into the dataSource Array and then continues with the next one?
Thanks and stay healthy!
Upvotes: 0
Views: 257
Reputation: 437552
It is exceedingly hard to be specific because we do not have information about your data source logic, the types, etc. But, then again, I do not think we want to get into that here, anyway.
So, some general observations:
You should use DispatchGroup
in the loop. E.g.,
let group = DispatchGroup()
for i in ... {
group.enter()
someAsyncMethod { completion in
defer { group.leave() }
...
}
}
group.notify(queue: .main) {
...
}
As you can see, I have removed that if (i == commentReplyArray.count - 1) { ... }
test because you want these to run in parallel and just because the “last” one finished doesn't mean that they've all finished. Use the DispatchGroup
and its notify
method to know when they're all done.
I am suspicious about that + 1
logic in your dataSource.insert
call (we live in a zero-based-index world). E.g. the first item you insert should have an index of 0
, not 1
. (And if you are doing that + 1
logic because you have some extra cell in your tableview/collection view, I would suggest not entangling that offset index logic inside this routine, but let your “data source” take care of that.)
That probably doesn't matter because you really want to refactor this data source, anyway, so it doesn't matter the order that the fetchDetailsAboutComent
completion handlers are called. E.g., build a local dictionary, and when done, build your sorted array and pass that back:
// dictionary for results, so order doesn't matter
var results: [Int: CommentReply] = [:] // I don't know what the type of your comment/reply is, so adjust this as needed
let group = DispatchGroup()
for i in 0..<20 {
group.enter()
someAsyncMethod { completion in
defer { group.leave() }
...
results[i] = ...
}
}
group.notify(queue: .main) {
// now build array from the dictionary
let array = (0..<20).compactMap { results[i] }
dataSource?.insert(array)
...
}
If you really want to call the data source as results come in, you can theoretically do that, but you want to make sure that you're not just inserting into an array but rather that the dataSource
object can handle the results as they come in, out of order.
You suggest that you want the loop to “pause” for one request until the prior one finishes, and I would strenuously advise against that pattern, as it will make the process far slower (basically compounding network latency effects). You really want logic that can let the requests run in parallel.
Upvotes: 1