链式网络请求 RXSwift

时间:2021-03-22 14:25:16

标签: swift rx-swift combine

嘿,我在 HackingWithSwift 教程中学到了如何使用 Combine 发出链接的网络请求(请参阅下面的代码)。现在我将使用 RXSwift 构建相同的逻辑,但我不知道如何像在 Combine 中一样获取/订阅以获得最终结果。

组合:

//Combine code
func fetch<T: Decodable>(_ url: URL, defaultValue: T) -> AnyPublisher<T, Never> {
       let decoder = JSONDecoder()
       decoder.dateDecodingStrategy = .iso8601

       return URLSession.shared.dataTaskPublisher(for: url)
           .retry(1)
           .map(\.data)
           .decode(type: T.self, decoder: decoder)
           .replaceError(with: defaultValue)
           .receive(on: DispatchQueue.main)
           .eraseToAnyPublisher()
   }

//call fetch method and get the end result 
fetch(url, defaultValue: [URL]())
        .flatMap { urls in
           urls.publisher.flatMap { url in
              fetch(url, defaultValue: [NewsItem]())
           }
        }
        .collect()
        .sink { values in
            let allItems = values.joined()
            items = allItems.sorted { $0.id > $1.id }
        }
        .store(in: &requests)



//RXSwift code
func fetchWithRX<T: Decodable>(_ url: URL, defaultValue: T) -> Observable<T> {
       let decoder        = JSONDecoder()
       decoder.dateDecodingStrategy = .iso8601
       let request        = URLRequest(url: url)
       
       return URLSession.shared.rx.response(request: request)
           .retry(1)
           .map(\.data)
           .decode(type: T.self, decoder: decoder)
           .debug()
           .catchAndReturn(defaultValue)
           .observe(on: MainScheduler.instance)
   }


//call fetch2 method 
Now I want to subscribe to the values like in the first fetch method with flatMap..collect..sink etc.

fetchWithRX(url, defaultValue:  [URL]()) 

1 个答案:

答案 0 :(得分:2)

我会这样写模拟:

fetchWithRX(url, defaultValue: [URL]())
    .flatMap { urls in
        Observable.zip(urls.map { fetchWithRX($0, defaultValue: [NewsItem]()) })
    }
    .map { $0.flatMap { $0 }.sorted { $0.id > $1.id } }
    .subscribe(onNext: { values in
        items = values
    })
    .disposed(by: requests)

通过这种方式,我将所有逻辑移动到一个 map 闭包中,该闭包可以移动到一个函数中以实现可测试性。尽量减少 flatMapsubscribe 中的代码量,以提高代码的可测试性。

或者你可以这样写:

fetchWithRX(url, defaultValue: [URL]())
    .flatMap { urls in
        Observable.zip(urls.map { fetchWithRX($0, defaultValue: [NewsItem]()) })
    }
    .subscribe(onNext: { values in
        let allItems = values.joined()
        items = allItems.sorted { $0.id > $1.id }
    })
    .disposed(by: requests)

您可以在这篇文章中了解有关组合 observable 的更多信息:Recipes for Combining Observables in RxSwift

URLSession 也有一个运算符 data(request:),它只会发出数据,因此您不必映射到转储结果对象。像这样:

func fetchWithRX<T: Decodable>(_ url: URL, defaultValue: T) -> Observable<T> {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601
    return URLSession.shared.rx.data(request: URLRequest(url: url))
        .retry(1)
        .decode(type: T.self, decoder: decoder)
        .catchAndReturn(defaultValue)
        .observe(on: MainScheduler.instance)
}

我突然想到您可能正在寻找原始组合代码的直接推论...上述示例将具有相同的最终输出,但它们的工作方式略有不同...

这是直接翻译:

fetchWithRX(url, defaultValue: [URL]())
    .flatMap { urls in
        // Observable.from(urls) works like urls.publisher 
        Observable.from(urls).flatMap { url in
            fetchWithRX(url, defaultValue: [NewsItem]())
        }
    }
    .toArray() // works like collect(). However, toArray() returns a Single rather than a generic Observable.
    .subscribe(onSuccess: { values in
        let allItems = values.joined()
        items = allItems.sorted { $0.id > $1.id }
    })
    .disposed(by: requests)

不同之处在于其他示例保留了新闻条目的顺序,而这则没有。由于您无论如何都在收集和排序,因此最终输出是相同的。如果您在观察输出之前不使用 collect()/toArray(),您只会看到差异。