2

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()?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Michael Salmon
  • 1,056
  • 7
  • 15

1 Answers1

2

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)
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks, it's seems a pity that you needed to resort to objective c to get a Swift framework to work. – Michael Salmon Jul 27 '19 at 04:45
  • Well you seemed to want to use KVO. I can rewrite it using PassthroughSubject if you like. Shiver me timbers, it’s only an example. Take what helps you and leave the rest. – matt Jul 27 '19 at 04:47
  • What I didn't show is that the hasChanged property is a PassthroughSubject publisher, my bad sorry. – Michael Salmon Jul 27 '19 at 05:00
  • Well it doesn’t matter, a publisher is a publisher. That’s the point. – matt Jul 27 '19 at 05:02
  • OK, changed it to `@Published` instead so we are not dependent on KVO, I hope that satisfies you – matt Jul 27 '19 at 13:03
  • Thanks, that looks better! – Michael Salmon Jul 27 '19 at 15:02
  • Cool, but keep in mind that the point is that the parts are interchangeable. A publisher is just a publisher. An operator is just an operator. A subscriber is just a subscriber. Mix and match. The point is how you put together the tiny pieces _you_ want into a pipeline that does what _you_ want. The example is just an example. – matt Jul 27 '19 at 15:19