Swift 合并链请求

时间:2021-03-19 10:26:26

标签: swift combine

我正在尝试使用 combine 将两个请求链接在一起。代码很粗糙,但是我需要调用两个api请求。一种用于获取计划数据,一种用于获取实时数据。我能够获得实时数据(第二次请求),但是我如何获得日程数据(第一次请求)?我很难理解如何使用 combine 将两个请求链接在一起,这是我第一次需要将 combine 用于我正在处理的小部件。我对 Swift 还很陌生,所以我的术语可能缺乏。

我的最后一个代码示例不正确,我的问题不清楚。我有两个publishers第二个取决于第一个。我对如何处理来自我的第一个 publisher 以及第二个数据的 .flatMap 中的数据的理解仍然不清楚。它是否需要是 ObservableObject 类并为数据提供 @Published 变量?我是否使用 .assign.sink 从我的可编码数据 ScheduleLive 中获取数据?文章创建自定义 extensions 并将 API 数据更改为 nested types 时对我来说似乎有点太先进了。

新的示例代码

import Foundation
import Combine

class DataGroup {
    // How to get data from Schedule and Live codable data, do I use a variable and .assign or .sink?
    // Where do I put the subscriber?
    
    func requestSchedule(_ teamID : Int) -> AnyPublisher<Schedule, Error> {
        let url = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule?teamId=\(teamID)")!
        return URLSession
            .shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: Schedule.self, decoder: JSONDecoder())
            .flatMap {self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")}
            /*
            .flatMap {URLSession.shared.dataTaskPublisher(for: URL(string: $0.dates.first?.games.first?.link ?? "")!)}
            */
            .eraseToAnyPublisher()
    }
    

    // Remove and put into flatMap URLSession.shared.dataTaskPublisher?
    func fetchLiveFeed(_ link: String) -> AnyPublisher<Live, Error> {
        let url = URL(string: "https://statsapi.web.nhl.com\(link)")!
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: Live.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

import Foundation
import Combine

class CombineData {
    var schedule: Schedule? // Get schedule data alongside live data
    var live: Live?
    
    private var cancellables = Set<AnyCancellable>()
    
    func fetchSchedule(_ teamID: Int, _ completion: @escaping (/* Schedule, */Live) -> Void) {
        let url = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule?teamId=\(teamID)")!
        URLSession.shared.dataTaskPublisher(for: url)
            .map { $0.data }
            .decode(type: Schedule.self, decoder: JSONDecoder())
            .flatMap { self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "") }
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { _ in }) { data in
                // How to get both schedule data and live data here?
                //self.schedule = ?
                self.live = data
                print(data)
                completion(self.schedule!, self.live!)
            }.store(in: &cancellables)
    }

    func fetchLiveFeed(_ link: String) -> AnyPublisher<Live, Error> {
        let url = URL(string: "https://statsapi.web.nhl.com\(link)")!
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: Live.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

1 个答案:

答案 0 :(得分:0)

一般的想法是使用 flatMap 进行链接,这就是您所做的,但是如果您还需要原始值,则可以返回一个 Zip 发布者(带有 {{1} } operator) 将两个结果放入一个元组中。

其中一个发布者是第二个请求,另一个应该只发出值。您通常可以使用 .zip 执行此操作,但您必须确保其失败类型(Just(v))与其他发布者匹配。您可以将其失败类型与 Never 匹配:

.setFailureType(to:)

或者,您可以使用 publisher1 .flatMap { one in Just(one).setFailureType(to: Error.self) // error has to match publisher2 .zip(publisher2(with: one)) } .sink(receiveCompletion: { completion in // ... }, receiveValue: { (one, two) in // ... }) 来推断错误(但可能看起来有些奇怪):

Result.Publisher

所以,在你的情况下,它将是这样的:

.flatMap { one in
   Result.Publisher(.success(one))
      .zip(publisher2)
}