Michael Salmon
Michael Salmon

Reputation: 1184

Swift Combine: Alternatives too eraseToAnySubscriber?

I am trying to use Combine to update a colour when my red, green or blue variables change. The examples I have looked at use sink() and that seems appropriate for me but eraseToAnySubscriber is MIA and I can't find an alternate.

What seems to work is to use an assign() to a computed variable but that seems like a bit of a hack.


init() {
        redCancellable = red.hasChanged.receive(on: RunLoop.main).assign(to: \.rgbUpdated, on: self)
    }

Is there any way to save the value returned by sink()?

Upvotes: 2

Views: 659

Answers (1)

matt
matt

Reputation: 535231

This sounds like a job for CombineLatest. And yes, sink is a perfectly good way to dispose of the end of the pipeline in whatever way you like.

Here's a simple example. I'll start with an object that has r, g, and b variables:

class ColorObject {
    @Published var r : CGFloat = 1
    @Published var g : CGFloat = 1
    @Published var b : CGFloat = 1
}

Now imagine that somewhere we have an instance of that object; call it colorObject. Then we can configure a publisher:

let rpub = colorObject.$r
let gpub = colorObject.$g
let bpub = colorObject.$b
let colorpub = Publishers.CombineLatest3(rpub,gpub,bpub)
    .map { UIColor(red: $0.0, green: $0.1, blue: $0.2, alpha: 1) }

The result is that every time colorObject's r or g or b changes, a UIColor comes down the pipeline. Now we can receive a notification from colorpub by subscribing to it with sink and dispose of the result however we like. Let's set some interface object's color to that color:

let sink = colorpub.sink { self.view.backgroundColor = $0 }

Alternatively, I could write it using assign, which perhaps is cleaner, though backgroundColor is an Optional so I have to interpose a map operator because keyPaths are not covariant:

let assign = colorpub.map{Optional($0)}
    .assign(to: \.backgroundColor, on: self.view)

Now whenever colorObject's r, g, or b changes, our view's color changes accordingly.

This is not the only way to accomplish this goal — far from it! But it's a simple example of getting things done with Combine. A possibly useful variant would be to move the colorpub publisher up into the ColorObject; that way, the ColorObject vends the color, directly, itself:

class ColorObject {
    @Published var r : CGFloat = 1
    @Published var g : CGFloat = 1
    @Published var b : CGFloat = 1
    lazy var colorpub = Publishers.CombineLatest3($r,$g,$b)
        .map { UIColor(red: $0.0, green: $0.1, blue: $0.2, alpha: 1) }
}

This changes nothing about the sink or assign:

let sink = colorObject.colorpub.sink { // ... whatever
// or
let assign = colorObject.colorpub.map{Optional($0)}
    .assign(to: \.backgroundColor, on: self.view)

Upvotes: 2

Related Questions