Combine:链式请求与依赖,保留两个响应

Combine: Chain requests with dependency, keep both responses

我正在努力思考 Combine 中的这个调用。

我有两个型号和电话。一个是位置数据数组,第二个是 OpenWeather 响应的数组。我需要的是将第一次呼叫响应中的纬度和经度传递到第二次呼叫中。同时我需要保留两个调用的响应。

请记住,这是我的第一个链接请求。

enum callAPI {
static let agent = Agent()
static let url1 = URL(string: "**jsonURL**")!
static let url2 = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&APPID=**APIkey**&unites=metric")! }

extension callAPI {
    
static func places() -> AnyPublisher<[Place], Error> {
    return run(URLRequest(url: url1))
}

static func weather(latitude: Double, longitude: Double) -> AnyPublisher<[ResponseBody], Error> {
    return run(URLRequest(url: url2))
}

static func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<T, Error> {
    return agent.run(request)
        .map(\.value)
        .eraseToAnyPublisher()
}}

func chain() {
let places = callAPI.places()
let firstPlace = places.compactMap { [=10=].first }
let weather = firstPlace.flatMap { place in
    callAPI.weather(latitude: place.latitude, longitude: place.longitude)
}

let token = weather.sink(receiveCompletion: { _ in },
                        receiveValue: { print([=10=]) })

RunLoop.main.run(until: Date(timeIntervalSinceNow: 10))

withExtendedLifetime(token, {})}

这是型号:

struct Place: Decodable, Identifiable {
let id: Int
let name: String
let description: String
let latitude, longitude: Double
let imageURL: String }

struct ResponseBody: Decodable {
var coord: CoordinatesResponse
var weather: [WeatherResponse]
var main: MainResponse
var name: String
var wind: WindResponse

    struct CoordinatesResponse: Decodable {
    var lon: Double
    var lat: Double
}

    struct WeatherResponse: Decodable {
    var id: Double
    var main: String
    var description: String
    var icon: String
}

    struct MainResponse: Decodable {
    var temp: Double
    var feels_like: Double
    var temp_min: Double
    var temp_max: Double
    var pressure: Double
    var humidity: Double
}

    struct WindResponse: Decodable {
    var speed: Double
    var deg: Double
}}

extension ResponseBody.MainResponse {
var feelsLike: Double { return feels_like }
var tempMin: Double { return temp_min }
var tempMax: Double { return temp_max }}

您将无法按照您尝试的方式链接请求并仍然捕获所有结果。

这样想。通过链接 Combine 运算符,您正在构建一个管道。您可以决定将什么放入管道的输入中,也可以将管道输出的任何内容转储到 sink 中,您可以在其中看到结果,但不能通过管道以查看中间值(至少在我们将要到达的管道中切割 window 时不会)。

这是您的代码:

let places = callAPI.places()
let firstPlace = places.compactMap { [=10=].first }
let weather = firstPlace.flatMap { place in
    callAPI.weather(latitude: place.latitude, longitude: place.longitude)
}

let token = weather.sink(receiveCompletion: { _ in },
                        receiveValue: { print([=10=]) })

这些变量每个都有一部分管道(不是将流经管道的值),您正在将管道拧在一起,在每个变量中放置越来越长的部分。

如果我想让整个管道更明显一点,可以这样写:

let cancellable = callAPI.places()
   .compactMap { [=11=].first }
   .flatMap { place in
    callAPI.weather(latitude: place.latitude, longitude: place.longitude)
   }
   .sink(receiveCompletion: { _ in },
         receiveValue: { print([=11=]) })

(请注意,可能无法按原样编译...我在答案编辑器中将其整合在一起)

当您直接链接运算符时,很明显您没有任何机会捕获中间结果。对于您的管道,进入管道的内容来自网络。您在 sink 中捕捉到流出管道的东西。但是请注意,您如何只能在作为管道本身一部分的闭包中查看流经管道的“东西”。

现在,如果您真的想将 window 切入管道以提取中间内容,您需要其中一个可以将值推出管道的闭包。在这种情况下,要获取 Places 的数组,您可以使用 handleEvents 来完成。它看起来像这样:

var allPlaces : [Place]?

callAPI.places()
    .handleEvents(receiveOutput: { allPlaces = [=12=] })
    .compactMap { [=12=].first }
    ...

在此代码中,您捕获了 receiveOutput 事件并将结果偷偷放入附近的变量中。

handleEvents在我看来,就是那些“实力大,责任大”的运营商之一。在这种情况下,它会让你做你要求做的事,但我不确定你是否应该这样做。

将运算符链接在一起的全部意义在于生成的管道“应该”没有 side-effects。在这种情况下,handleEvents 用于显式引入 side-effect(设置 allPlaces 变量)。从本质上讲,在我看来,这是一种代码味道,表明您可能需要重新考虑您的设计。