代码之家  ›  专栏  ›  技术社区  ›  Max Kraev

Swift中的嵌套异步调用

  •  3
  • Max Kraev  · 技术社区  · 6 年前

    一般来说,我对编程有点陌生,所以我有一个可能很简单的问题。实际上,写作可以帮助我更快地发现问题。

    无论如何,我有一个具有多个异步调用的应用程序,它们是这样嵌套的:

    InstagramUnoficialAPI.shared.getUserId(from: username, success: { (userId) in
    
        InstagramUnoficialAPI.shared.fetchRecentMedia(from: userId, success: { (data) in
    
            InstagramUnoficialAPI.shared.parseMediaJSON(from: data, success: { (media) in
    
                    guard let items = media.items else { return }
                    self.sortMediaToCategories(media: items, success: {
    
                        print("success")
    
              // Error Handlers
    

    看起来很可怕,但这不是重点。我会调查Promise Kit,一旦我成功了。

    我需要 sortMediaToCategories 等待完成,然后重新加载我的收藏视图。然而,在 SortMedia类别 我还有另一个嵌套函数,它也是异步的,并且有一个for-in循环。

    func sortMediaToCategories(media items: [StoryData.Items],
                               success: @escaping (() -> Swift.Void),
                               failure: @escaping (() -> Swift.Void)) {
    
        let group = DispatchGroup()
        group.enter()
    
    
        for item in items {
    
    
            if item.media_type == 1 {
    
                guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
    
                mediaToStorageDistribution(withImageUrl: url,
                                           videoUrl: nil,
                                           mediaType: .jpg,
                                           takenAt: item.taken_at,
                                           success: { group.notify(queue: .global(), execute: {
    
                                            self.collectionView.reloadData()
                                               group.leave()
    
                                           })  },
                                           failure: { print("error") })
    
     //....
    

    显然,我不能每次都重新加载集合视图,所以我需要等待循环完成,然后重新加载。

    我正在尝试使用调度组,但正在与之斗争。你能帮我一下吗?任何简单的例子和建议都将不胜感激。

    3 回复  |  直到 6 年前
        1
  •  10
  •   CouchDeveloper    6 年前

    您面临的问题是一个常见的问题:有多个异步任务并等待所有任务完成。

    有几种解决方案。最简单的是利用 DispatchGroup :

    func loadUrls(urls: [URL], completion: @escaping ()->()) {
        let grp = DispatchGroup()
    
        urls.forEach { (url) in
            grp.enter()
            URLSession.shared.dataTask(with: url) { data, response, error in
                // handle error
                // handle response
                grp.leave()
            }.resume()
        }
    
        grp.notify(queue: DispatchQueue.main) {
            completion()
        }
    }
    

    功能 loadUrls 是异步的,需要一个URL数组作为输入,并需要一个完成处理程序,在所有任务完成后将调用该处理程序。这将通过 DispatchGroup调度组 如所示。

    关键是,要确保 grp.enter() 将被调用 之前 调用任务和 grp.leave 当任务 已完成 enter leave 应保持平衡。

    grp.notify 最后注册一个闭包,当DispatchGroup grp 结余(即其内部计数器达到零)。

    不过,此解决方案有几个注意事项:

    • 所有任务将几乎同时启动并同时运行
    • 此处未显示通过完成处理程序报告所有任务的最终结果。其实施将需要适当的同步。

    对于所有这些警告,都有一些很好的解决方案,应该利用合适的第三方库来实现。例如,您可以将任务提交给某种类型的“executer”,该“executer”控制并发运行的任务数量(类似于操作队列和异步操作的匹配)。

    许多“Promise”或“Future”库简化了错误处理,还可以通过一个函数调用帮助您解决此类问题。

        2
  •  1
  •   Kamran    6 年前

    你可以 reloadData 当最后一项以这种方式调用成功块时。

    let lastItemIndex = items.count - 1
    
    for(index, item) in items.enumerated() {
    
    if item.media_type == 1 {
    
      guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
    
      mediaToStorageDistribution(withImageUrl: url,
                                 videoUrl: nil,
                                 mediaType: .jpg,
                                 takenAt: item.taken_at,
                                 success: { 
                                   if index == lastItemIndex {
                                      DispatchQueue.global().async {
    
                                        self.collectionView.reloadData()
                                      }         
                                    } 
                                 },
                                 failure: { print("error") })
      }
    
        3
  •  1
  •   pkorosec    6 年前

    你必须移动团队。在循环内调用enter()。必须平衡进入和离开的呼叫。如果您对mediaToStorageDistribution函数的成功和失败的回调是独占的,那么您还需要在失败时离开组。当所有调用enter的块离开时,将调用组notify。您可能想用一个中断来替换guard语句中的返回,只跳过缺少URL的项目。现在,您正在从整个sortMediaToCatgories函数返回。

    func sortMediaToCategories(media items: [StoryData.Items], success: @escaping (() -> Void), failure: @escaping (() -> Void)) {
    
        let group = DispatchGroup()
    
        for item in items {
            if item.media_type == 1 {
                guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else { break }
    
                group.enter()
                mediaToStorageDistribution(withImageUrl: url,
                                           videoUrl: nil,
                                           mediaType: .jpg,
                                           takenAt: item.taken_at,
                                           success: { group.leave() },
                                           failure: {
                                              print("error")
                                              group.leave()
                })
            }
         }
    
        group.notify(queue: .main) {
            self.collectionView.reloadData()
        }
     }