I've just updated to Xcode 13.2.1 and now have access to async await and I'm trying to find places I can "convert" from Combine over to async await.
I want to achieve the following...
Given a type like...
struct Person {
let name: String
func fetchAvatar(completion: @escaping (UIImage?) -> Void) {
// fetch the image from the web and pass it into the completion.
}
}
I currently have a function like this...
func fetchAllTheAvatars(people: [Person], completion: ([UIImage]) -> Void) {
Publisher.MergeMany(
people.map { person in
Future<UIImage?, Never> { promise in
person.fetchAvatar { promise(.success($0)) }
}
}
)
.compactMap { $0 }
.collect()
.sink { completion($0) }
.store(in: &cancellables )
}
Now... it looks to me like this could be a good candidate for moving to using async await and AsyncSequence
maybe...?!? It doesn't have to be ideal though, I just want to get a feel of how to use them. I'm used to async await
in JS and TS and this just seems a little bit different. :D
I added a wrapper function to my Person...
func fetchAvatar() async -> UIImage? {
await withCheckedContinuation { continuation in
fetchAvatar { image in
continuation.resume(returning: image)
}
}
}
But now I'm stuck on how to update my fetchAllTheAvatars
function.
func fetchAllTheAvatars(people: [Person]) async -> [UIImage] {
people.map { ...???... }
}
Everywhere I have seen online seems to use for await line in url.lines { ... }
but I don't yet have an AsyncSequence. I need to somehow "convert" my non-async array of Person
into an AsyncSequence
of () -> Image?
.
Is that possible? Am I going about this entirely the wrong way?
Thanks
CodePudding user response:
The standard pattern is TaskGroup
. Add your tasks for the individual images, and then await
in a for
loop, map
, or, in this case, 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
}
}
}
}
Note, because the order is not guaranteed and because some of your Person
may not return an image, my implementation returns an efficient, order-independent, structure (i.e., a dictionary).
Needless to say, the above assumes that you make Person
conform to Identifiable
:
struct Person: Identifiable {
let id = UUID()
let name: String
func fetchAvatar() async -> UIImage? { … }
}