首页 > 解决方案 > 链式网络请求 RXSwift

问题描述

嘿,我在 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]()) 

标签: swiftrx-swiftcombine

解决方案


我会这样写模拟:

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闭包中,该闭包可以移动到一个函数中以实现可测试性。尽量减少 a flatMaporsubscribe中的代码量,以提高代码的可测试性。

或者你可以这样写:

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)

你可以在这篇文章中了解更多关于组合 observables 的信息: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()


推荐阅读