Home > front end >  How to chain together two Combine publishers in Swift and keep the cancellable object
How to chain together two Combine publishers in Swift and keep the cancellable object

Time:01-06

Does anyone know how to chain together two publishers with Swift Combine and keep the cancellable so that I can cancel the pipeline later? I have a method that accepts input from a publisher and outputs to a publisher:

static func connect(inputPublisher: Published<String>.Publisher, outputPublisher: inout Published<String>.Publisher) -> AnyCancellable {
    inputPublisher.assign(to: &outputPublisher)

    // Unfortunately assign consumes cancellable
}

Note that this method cannot access the wrapped properties directly. The publishers must be passed as arguments.

CodePudding user response:

You cannot do that with assign(to:), since it binds the lifetime of the subscription to the lifetime of the Published upstream (inputPublisher in your case) and hence you cannot manually cancel the subscription.

Instead, you can use assign(to:on:), which takes an object and a key path and whenever the upstream emits, it assigns the value to the property represented by the key path on the object.

inputPublisher.assign(to:on:)

class PublishedModel {
    @Published var value: String

    init(value: String) {
        self.value = value
    }
}

let outputModel = PublishedModel(value: "")

// this returns an `AnyCancellable` that you can cancel
PublishedModel(value: "initial").$value.assign(to: \.value, on: outputModel)

You can also create a convenience method for this

func assign<Root, Value>(published: Published<Value>.Publisher, to keyPath: ReferenceWritableKeyPath<Root, Value>, on object: Root) -> AnyCancellable {
    published.assign(to: keyPath, on: object)
}

let outputModel = PublishedModel(value: "")
let inputModel = PublishedModel(value: "initial")
assign(published: inputModel.$value, to: \.value, on: outputModel)

CodePudding user response:

If I understand correctly, you want to link two publishers but with the option to break that link at some point in the future.

I would try using sink on the inputPublisher, since that function gives me a cancellable, and then a PassthroughSubject, since I wasn't able to figure out how to pass the value from sink directly to outputPublisher.

It would look something like this:

static func connect(inputPublisher: Published<String>.Publisher, outputPublisher: inout Published<String>.Publisher) -> AnyCancellable {
  let passthrough = PassthroughSubject<String, Never>()
  passthrough.assign(to: &outputPublisher)
  let cancellable = inputPublisher.sink { string in
    passthrough.send(string)
  }
  return cancellable
}

Disclaimer: I wrote this on a Playground and it compiles, but I didn't actually run it.

  •  Tags:  
  • Related