Swift - 循环中的异步调用

时间:2020-12-19 12:48:15

标签: swift loops asynchronous

希望你一切顺利。 我正在努力实现以下目标:

  1. 从数据库中获取数据数组(异步调用)
  2. 迭代获取的数据数组
  3. 获取有关每个对象的附加信息(异步调用)
  4. 创建一个包含所有信息的新数据数组并将其返回

目前,我有以下方法

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)

我的 fetchDetailsAboutCommentReply 函数:

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)
}

}

我的问题是如何更改我的代码,因此循环基本上“暂停”,直到我获取迭代对象的每个详细信息,将其添加到数据源数组中,然后继续下一个?

谢谢并保持健康!

1 个答案:

答案 0 :(得分:1)

要具体说明非常困难,因为我们没有关于您的数据源逻辑、类型等的信息。但话说回来,无论如何,我认为我们不想在这里讨论这个问题。

所以,一些一般性观察:

  • 您应该在循环中使用 DispatchGroup。例如,

    let group = DispatchGroup()
    for i in ... {
        group.enter()
        someAsyncMethod { completion in
            defer { group.leave() }
            ...
        }
    }
    group.notify(queue: .main) {
        ...
    }
    
  • 如您所见,我删除了 if (i == commentReplyArray.count - 1) { ... } 测试,因为您希望这些测试并行运行,并且仅仅因为“最后一个”完成并不意味着它们都已完成。使用 DispatchGroup 及其 notify 方法了解它们何时全部完成。

  • 我对您的 + 1 调用中的 dataSource.insert 逻辑表示怀疑(我们生活在一个基于零索引的世界)。例如。您插入的第一项的索引应该是 0,而不是 1。 (如果您正在执行 + 1 逻辑,因为您的 tableview/collection 视图中有一些额外的单元格,我建议不要在此例程中纠缠该偏移索引逻辑,而是让您的“数据源”处理.)

  • 这可能无关紧要,因为无论如何您确实想重构此数据源,因此调用 fetchDetailsAboutComent 完成处理程序的顺序无关紧要。例如,构建一个本地字典,完成后,构建您的排序数组并将其传回:

    // 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)
        ...
    }
    

    如果您真的想在结果出现时调用数据源,理论上您可以这样做,但您要确保不仅插入数组,而且 dataSource 对象可以无序处理结果。

    您建议您希望循环为一个请求“暂停”,直到前一个请求完成,我强烈建议不要使用这种模式,因为它会使过程变慢(基本上会加剧网络延迟效应)。您确实需要能够让请求并行运行的逻辑。