简体   繁体   中英

Swift - Async calls in loop

I hope you are doing fine. I am trying to achieve following thing:

  1. Fetch data array from database (async call)
  2. Iterate over fetched data array
  3. Fetch additional information about each object (async call)
  4. Create a new data array with all the information and return it back

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!

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. Eg,

     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). Eg 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. Eg, 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.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM