如果您有一个生成值的异步方法,那么迭代该方法以生成值数组的最有效方法是什么?
protocol ImageFetching {
var title: String { get }
func fetch(from url: URL) async throws -> UIImage
func fetch(from urls: [URL]) async throws -> [UIImage]
}
这个类通过使用一个遍历 url 并调用其单值方法的 TaskGroup 来生成一个数组fetch(from:) -> UIImage
。
class ImageService: ImageFetching {
enum Error: Swift.Error {
case badData
}
var title: String { String(describing: Self.self) }
func fetch(from url: URL) async throws -> UIImage {
let session = URLSession(configuration: .default)
let data = try await session.data(from: url).0
guard let image = UIImage(data: data) else { throw Error.badData }
return image
}
func fetch(from urls: [URL]) async throws -> [UIImage] {
return try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
try await self.fetch(from: url)
}
}
var items: [UIImage] = []
for try await item in group {
items.append(item)
}
return items
}
}
}
该类不调用单值方法。相反,它URLSession.data(from:)
手动UIImage(data:)
调用。奇怪的是,这比ImageService
上面的实现效率低。
class ImageServiceUnrolled: ImageService {
override func fetch(from urls: [URL]) async throws -> [UIImage] {
let session = URLSession(configuration: .default)
return try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
let data = try await session.data(from: url).0
guard let image = UIImage(data: data) else { throw Error.badData }
return image
}
}
var items: [UIImage] = []
for try await item in group {
items.append(item)
}
return items
}
}
}
此类使用AsyncStream
带有展开初始化程序的 an。这是效率最低的方法。
class ImageServiceAsyncStreamUnfolding: ImageService {
override func fetch(from urls: [URL]) async throws -> [UIImage] {
var it = urls.makeIterator()
return try await AsyncStream(unfolding: {
it.next()
})
.map { url -> UIImage in
try await self.fetch(from: url)
}
.reduce(into: [], { partialResult, image in
partialResult.append(image)
})
}
}
此类使用AsyncStream
带有延续初始值设定项的 an。它比上面的实现稍微高效一些。
class ImageServiceAsyncStreamContinue: ImageService {
override func fetch(from urls: [URL]) async throws -> [UIImage] {
return try await AsyncStream.init { continuation in
Task.detached {
for url in urls {
continuation.yield(url)
}
continuation.finish()
}
}
.map { url -> UIImage in
try await self.fetch(from: url)
}
.reduce(into: [], { partialResult, image in
partialResult.append(image)
})
}
}
我很好奇为什么第一个实现是最有效的,尤其是为什么它比第二个实现更有效。至于 AsyncStream 的实现,它们似乎效率很低。
正在使用TaskGroup
最好的方法来解决这个问题吗?
这是我在每次迭代时从网络下载 10 张图像时观察到的时间(以毫秒为单位)。
Elapsed [ImageService]:12.93
Elapsed [ImageService]:13.00
Elapsed [ImageService]:16.74
Elapsed [ImageServiceUnrolled]:16.41
Elapsed [ImageServiceUnrolled]:19.06
Elapsed [ImageServiceUnrolled]:17.88
Elapsed [ImageServiceAsyncStreamUnfolding]:28.76
Elapsed [ImageServiceAsyncStreamUnfolding]
: ImageServiceAsyncStreamUnfolding]:28.47
Elapsed [ImageServiceAsyncStreamContinue]:29.55
Elapsed [ImageServiceAsyncStreamContinue]:28.53
Elapsed [ImageServiceAsyncStreamContinue]:27.05