如何将多个网络调用的响应合并到一个数组中?有或没有联合?

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

我已经使用常规完成处理程序创建了两个函数

还有它们的 Combine 变体

两种方法都很好,return是预期的结果。我们如何合并它们?

fetchCodes() 方法将 json 解码为字典并根据键创建一个数组。

获取代码后:来自 fetchCodes() 的 [String] 想要做这样的事情:

如何实现?

我尝试了 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失败,那么其他的都被取消。对于回调版本,情况并非如此。相反,其他人将完成然后丢弃下载的数据。