Swift 从对象数组映射到异步函数数组并等待它们全部

Swift map from array of objects to array of async functions and await them all

我刚刚更新到 Xcode 13.2.1,现在可以访问 async await,我正在尝试找到可以从 Combine “转换”到 async await 的地方。

我想实现以下...

给定一个类型...

struct Person {
  let name: String

  func fetchAvatar(completion: @escaping (UIImage?) -> Void) {
    // fetch the image from the web and pass it into the completion.
  }
}

我目前有这样的功能...

func fetchAllTheAvatars(people: [Person], completion: ([UIImage]) -> Void) {
  Publisher.MergeMany(
    people.map { person in
      Future<UIImage?, Never> { promise in
        person.fetchAvatar { promise(.success([=12=])) }
      }
    }
  )
  .compactMap { [=12=] }
  .collect()
  .sink { completion([=12=]) }
  .store(in: &cancellables )
}

现在......在我看来这可能是转向使用异步等待的一个很好的候选者并且AsyncSequence也许......?!?虽然它不一定是理想的,但我只是想了解如何使用它们。我习惯在 JS 和 TS 中使用 async await,这似乎有点不同。 :D

我为我的 Person 添加了一个包装函数...

func fetchAvatar() async -> UIImage? {
  await withCheckedContinuation { continuation in
    fetchAvatar { image in
      continuation.resume(returning: image)
    }
  }
}

但现在我被困在如何更新我的 fetchAllTheAvatars 功能上。

func fetchAllTheAvatars(people: [Person]) async -> [UIImage] {
  people.map { ...???... }
}

我在网上看到的所有地方似乎都在使用 for await line in url.lines { ... },但我还没有 AsyncSequence。我需要以某种方式将 Person 的非异步数组“转换”为 () -> Image?.

AsyncSequence

这可能吗?我这样做完全是错误的方式吗?

谢谢

标准模式是TaskGroup。为单个图像添加任务,然后在 for 循环中 awaitmap,或者在本例中,reduce:

func fetchAllTheAvatars(people: [Person]) async -> [Person.ID: UIImage] {
    await withTaskGroup(of: (Person.ID, UIImage?).self) { group in
        for person in people {
            group.addTask { await (person.id, person.fetchAvatar()) }
        }
        
        return await group.reduce(into: [Person.ID: UIImage]()) { (dictionary, result) in 
            if let image = result.1 {
                dictionary[result.0] = image
            }
        }
    }
}

请注意,由于不能保证顺序,并且您的某些 Person 可能不是 return 图片,因此我的实施 return 是有效的,order-independent,结构(即字典)。

不用说了,上面假设你让Person符合Identifiable:

struct Person: Identifiable {
    let id = UUID()
    let name: String
    
    func fetchAvatar() async -> UIImage? { … }
}