Search code examples
swiftcombine

Access a publisher created at the top of a Combine chain further down the chain


I am trying to create a function that will return me a struct constructed from two publishers within a chain of Combine calls, something like this pseudocode

struct UpdatedPilotsInfo {
    let pilots: [JSON]
    let squad: JSON
}

func buildInfoIdeal() {
        func processPilots(squad: JSON) -> AnyPublisher<[JSON] , XWSImportError> { ... }
        func loadSquad() -> AnyPublisher<JSON, XWSImportError> { ... }
        
        let squadInfoPublisher: AnyPublisher<(JSON, [JSON]), XWSImportError> = JSONSection
            .SquadService_XWSImport_New
            .SquadService
            .loadSquad() // AnyPublisher<JSON, XWSImportError>
            .flatMap(processPilots) // AnyPublisher<[JSON], XWSImportError>
            .???    // AnyPublisher<(JSON, [JSON]), XWSImportError>
            .map{ tuple -> UpdatedPilotsInfo in
                return UpdatedPilotsInfo(squad: tuple.0, pilots: tuple.1)
            }.eraseToAnyPublisher()
}

My issue is that I don’t know how I can make an upstream publisher created at the top of the chain (loadSquad) available further down the chain. In the example above, I pass the output of the loadSquad() to processPilots() and I’d like to build a UpdatedPilotsInfo from the results of loadSquad() and processPilots(). I think that I can accomplish this by doing the following, but I’d like to have a simple chain instead of all this code:

    func buildInfo() -> AnPublisher<UpdatedPilotsInfo, XWSImportError> {
        func processPilots(squad: JSON) -> AnyPublisher<[JSON] , XWSImportError> { ... }
        func loadSquad() -> AnyPublisher<JSON, XWSImportError> { ... }
        
        let squadPublisher: AnyPublisher<JSON, XWSImportError> = JSONSection
            .SquadService_XWSImport_New
            .SquadService
            .loadSquad() // AnyPublisher<JSON, XWSImportError>
            .eraseToAnyPublisher()

        let updatedPilots: AnyPublisher<[JSON], XWSImportError> = squadPublisher
            .flatMap{ [weak self] squad -> AnyPublisher<[JSON], XWSImportError in
                let s = self
                return s.processPilots(squad)
            }
            .eraseToAnyPublisher()

        let zip = Publishers.Zip(squadPublisher, updatedPilots)
            .eraseToAnyPublisher()  // AnyPublisher<(JSON, [JSON]), XWSImportError>

        return zip.map{ tuple -> UpdatedPilotsInfo in 
            return UpdatedPilotsInfo(squad: tuple.0, pilots: tuple.1)
        }.eraseToAnyPublisher() 
    }

Is this something I can accomplish with an existing operator or do I need to create a custom operator?


Solution

  • Keep in mind that .flatMap { Just($0) } is exactly the same as .map { $0 }. And both are a no-op.

    This means you can pass down the parameter using just while combining it with some other publisher that does some actual work.

    loadSquad
        .flatMap { Publishers.Zip(Just($0).setFailureType(to: Error.self), processPilots($0)) }
        .map { UpdatedPilotsInfo(squad: $0.0, pilots: $0.1) }
        .eraseToAnyPublisher()
    

    So with the above, we are passing the response from loadSquad along with the response from processPilots inside the flatMap.