如何将多个网络调用的响应合并到一个数组中?有或没有联合?
How to merge multiple network calls’ responses into an array? With or without Combine?
与flagpedia.net/download/api
合作
它有两个端点:
1 - Returns [String:String] 代码国家/地区对字典,如 [“us”:”United States”] 作为 json
2 - Returns 国家代码和指定图像大小的图像数据,示例 url flagcdn.com/16x12/us.png
我已经使用常规完成处理程序创建了两个函数
fetchCodes(completion: @escaping (Result<[String],Error>) -> Void) { … }
fetchImage(forCode: String, completion: @escaping (Result<UIImage,Error>) -> Void) { … }
还有它们的 Combine 变体
fetchCodes() -> AnyPublisher<[String],Error> { … }
fetchImage(forCode: String) -> AnyPublisher<UIImage,Error> { … }
两种方法都很好,return是预期的结果。我们如何合并它们?
fetchCodes() 方法将 json 解码为字典并根据键创建一个数组。
获取代码后:来自 fetchCodes() 的 [String] 想要做这样的事情:
var results = [UIImage]()
for code in codes {
let image = fetchImage(forCode: code)
images.append(image)
}
如何实现?
我尝试了 Publishers.ManyMerge 和 flatMap 但没有成功。最终收到有关 return 类型不匹配的警告。
Sorry for the indentation, I’m posting this on mobile.
这是使用组合运算符执行此操作的好方法:
func fetchCodes() -> AnyPublisher<[String],Error> { fatalError() }
func fetchImage(forCode: String) -> AnyPublisher<UIImage,Error> { fatalError() }
func example() -> AnyPublisher<[String: UIImage], Error> {
let codesAndImages = keysAndValues(fetchImage(forCode:))
return fetchCodes()
.flatMap { codes in
combineLatest(codesAndImages(codes))
}
.map(Dictionary.init(uniqueKeysWithValues:))
.eraseToAnyPublisher()
}
func keysAndValues<A, B>(_ get: @escaping (A) -> AnyPublisher<B, Error>) -> ([A]) -> [AnyPublisher<(A, B), Error>] {
{ xs in
xs.map { x in
Just(x).setFailureType(to: Error.self)
.zip(get(x))
.eraseToAnyPublisher()
}
}
}
func combineLatest<A>(_ xs: [AnyPublisher<A, Error>]) -> AnyPublisher<[A], Error> {
xs.reduce(Empty<[A], Error>().eraseToAnyPublisher()) { state, x in
state.combineLatest(x)
.map { [=10=].0 + [[=10=].1] }
.eraseToAnyPublisher()
}
}
或者你可以像这样更讨人喜欢:
func example() -> AnyPublisher<[String: UIImage], Error> {
let codes = fetchCodes()
.share()
let images = codes.flatMap { zipAll([=11=].map(fetchImage(forCode:))) }
return Publishers.Zip(codes, images)
.map { zip([=11=], ) }
.map(Dictionary.init(uniqueKeysWithValues:))
.eraseToAnyPublisher()
}
func zipAll<A>(_ xs: [AnyPublisher<A, Error>]) -> AnyPublisher<[A], Error> {
xs.reduce(Empty<[A], Error>().eraseToAnyPublisher()) { state, x in
state.zip(x)
.map { [=11=].0 + [[=11=].1] }
.eraseToAnyPublisher()
}
}
没有 Combine 它会变得有点棘手:
func fetchCodes(completion: @escaping (Result<[String],Error>) -> Void) { }
func fetchImage(forCode: String, completion: @escaping (Result<UIImage,Error>) -> Void) { }
func example(_ completion: @escaping (Result<[String: UIImage], Error>) -> Void) {
fetchCodes { codesResult in
switch codesResult {
case let .success(codes):
loadImages(codes: codes, completion)
case let .failure(error):
completion(.failure(error))
}
}
}
func loadImages(codes: [String], _ completion: @escaping (Result<[String: UIImage], Error>) -> Void) {
var result = [String: UIImage]()
let lock = NSRecursiveLock()
let group = DispatchGroup()
for code in codes {
group.enter()
fetchImage(forCode: code) { imageResult in
switch imageResult {
case let .success(image):
lock.lock()
result[code] = image
lock.unlock()
group.leave()
case let .failure(error):
completion(.failure(error))
}
}
}
group.notify(queue: .main) {
completion(.success(result))
}
}
使用Combine版本,如果一个fetchImage失败,那么其他的都被取消。对于回调版本,情况并非如此。相反,其他人将完成然后丢弃下载的数据。
与flagpedia.net/download/api
它有两个端点:
1 - Returns [String:String] 代码国家/地区对字典,如 [“us”:”United States”] 作为 json
2 - Returns 国家代码和指定图像大小的图像数据,示例 url flagcdn.com/16x12/us.png
我已经使用常规完成处理程序创建了两个函数
fetchCodes(completion: @escaping (Result<[String],Error>) -> Void) { … }
fetchImage(forCode: String, completion: @escaping (Result<UIImage,Error>) -> Void) { … }
还有它们的 Combine 变体
fetchCodes() -> AnyPublisher<[String],Error> { … }
fetchImage(forCode: String) -> AnyPublisher<UIImage,Error> { … }
两种方法都很好,return是预期的结果。我们如何合并它们?
fetchCodes() 方法将 json 解码为字典并根据键创建一个数组。
获取代码后:来自 fetchCodes() 的 [String] 想要做这样的事情:
var results = [UIImage]()
for code in codes {
let image = fetchImage(forCode: code)
images.append(image)
}
如何实现?
我尝试了 Publishers.ManyMerge 和 flatMap 但没有成功。最终收到有关 return 类型不匹配的警告。
Sorry for the indentation, I’m posting this on mobile.
这是使用组合运算符执行此操作的好方法:
func fetchCodes() -> AnyPublisher<[String],Error> { fatalError() }
func fetchImage(forCode: String) -> AnyPublisher<UIImage,Error> { fatalError() }
func example() -> AnyPublisher<[String: UIImage], Error> {
let codesAndImages = keysAndValues(fetchImage(forCode:))
return fetchCodes()
.flatMap { codes in
combineLatest(codesAndImages(codes))
}
.map(Dictionary.init(uniqueKeysWithValues:))
.eraseToAnyPublisher()
}
func keysAndValues<A, B>(_ get: @escaping (A) -> AnyPublisher<B, Error>) -> ([A]) -> [AnyPublisher<(A, B), Error>] {
{ xs in
xs.map { x in
Just(x).setFailureType(to: Error.self)
.zip(get(x))
.eraseToAnyPublisher()
}
}
}
func combineLatest<A>(_ xs: [AnyPublisher<A, Error>]) -> AnyPublisher<[A], Error> {
xs.reduce(Empty<[A], Error>().eraseToAnyPublisher()) { state, x in
state.combineLatest(x)
.map { [=10=].0 + [[=10=].1] }
.eraseToAnyPublisher()
}
}
或者你可以像这样更讨人喜欢:
func example() -> AnyPublisher<[String: UIImage], Error> {
let codes = fetchCodes()
.share()
let images = codes.flatMap { zipAll([=11=].map(fetchImage(forCode:))) }
return Publishers.Zip(codes, images)
.map { zip([=11=], ) }
.map(Dictionary.init(uniqueKeysWithValues:))
.eraseToAnyPublisher()
}
func zipAll<A>(_ xs: [AnyPublisher<A, Error>]) -> AnyPublisher<[A], Error> {
xs.reduce(Empty<[A], Error>().eraseToAnyPublisher()) { state, x in
state.zip(x)
.map { [=11=].0 + [[=11=].1] }
.eraseToAnyPublisher()
}
}
没有 Combine 它会变得有点棘手:
func fetchCodes(completion: @escaping (Result<[String],Error>) -> Void) { }
func fetchImage(forCode: String, completion: @escaping (Result<UIImage,Error>) -> Void) { }
func example(_ completion: @escaping (Result<[String: UIImage], Error>) -> Void) {
fetchCodes { codesResult in
switch codesResult {
case let .success(codes):
loadImages(codes: codes, completion)
case let .failure(error):
completion(.failure(error))
}
}
}
func loadImages(codes: [String], _ completion: @escaping (Result<[String: UIImage], Error>) -> Void) {
var result = [String: UIImage]()
let lock = NSRecursiveLock()
let group = DispatchGroup()
for code in codes {
group.enter()
fetchImage(forCode: code) { imageResult in
switch imageResult {
case let .success(image):
lock.lock()
result[code] = image
lock.unlock()
group.leave()
case let .failure(error):
completion(.failure(error))
}
}
}
group.notify(queue: .main) {
completion(.success(result))
}
}
使用Combine版本,如果一个fetchImage失败,那么其他的都被取消。对于回调版本,情况并非如此。相反,其他人将完成然后丢弃下载的数据。