简体   繁体   English

Swift - 循环中的异步调用

[英]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)获取有关每个 object 的附加信息(异步调用)
  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:我的 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?我的问题是如何更改我的代码,所以循环基本上“暂停”,直到我获取迭代的 object 的所有详细信息,将其添加到 dataSource 数组中,然后继续下一个?

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.您应该在循环中使用DispatchGroup 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.如您所见,我删除了if (i == commentReplyArray.count - 1) {... } test 因为您希望这些并行运行并且仅仅因为“最后一个”完成并不意味着它们我都完成了。 Use the DispatchGroup and its notify method to know when they're all done.使用DispatchGroup及其notify方法可以知道它们何时完成。

  • I am suspicious about that + 1 logic in your dataSource.insert call (we live in a zero-based-index world).我对您的dataSource.insert调用中的+ 1逻辑持怀疑态度(我们生活在一个从零开始的索引世界中)。 Eg the first item you insert should have an index of 0 , not 1 .例如,您插入的第一个项目的索引应该是0 ,而不是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.) (如果你正在执行+ 1逻辑,因为你的 tableview/collection 视图中有一些额外的单元格,我建议不要在这个例程中纠缠偏移索引逻辑,而是让你的“数据源”来处理。)

  • 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.这可能无关紧要,因为无论如何您确实想重构此数据源,因此调用fetchDetailsAboutComent完成处理程序的顺序无关紧要。 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.如果你真的想在结果进来时调用数据源,理论上你可以这样做,但是你要确保你不只是插入到一个数组中,而是dataSource object 可以在结果进来时处理它们,乱序。

    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.您确实需要可以让请求并行运行的逻辑。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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