Swift 合并链请求

Swift combine chain requests

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

我上一个代码示例不正确,我的问题不清楚。我有两个 publishers 并且 第二个 取决于 第一个 。我的理解仍然不清楚如何处理我的第一个 publisher 中的数据以及第二个数据的 .flatMap 中的数据。是否需要 ObservableObject class 并且数据有 @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([=11=].dates.first?.games.first?.link ?? "")}
            /*
            .flatMap {URLSession.shared.dataTaskPublisher(for: URL(string: [=11=].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 { [=12=].data }
            .decode(type: Schedule.self, decoder: JSONDecoder())
            .flatMap { self.fetchLiveFeed([=12=].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()
    }
}

大体的思路是使用一个flatMap进行链接,你就是这样做的,但是如果你还需要原始值,你可以return一个Zip发布者(使用 .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)
}

因此,在您的情况下,它将是这样的:

URLSession.shared.dataTaskPublisher(for: url)
   .map(\.data)
   .decode(type: Schedule.self, decoder: JSONDecoder())
   .flatMap {
       Result.Publisher(.success([=12=]))
          .zip(self.fetchLiveFeed([=12=].dates.first?.games.first?.link ?? ""))
   }
   .sink(receiveCompletion: { completion in
        // ...
    }, receiveValue: { (schedule, live) in
        // ...
    })
   .store(in: &cancellables)