`Publisher.assign(to: Published<Output>.Publisher)` with optionals

This is nice: Apple Developer Documentation

…but it can be a pain because WritableKeyPath is a misnomer. (There still aren't set-only properties. :crying_cat_face:) What do people do when they want to use the method to set an optional with a non-optional?

You use map

x
  .map { $0 as Optional }
  .assign { ... }

Related thread–Combine assign to Optional property: alternate versions?

Thanks.

However, I need to set properties of the Published object, and Publishers.MapKeyPath doesn't work with that overload, so I think I'm stuck with sink. And if that's the case, map isn't necessary, because you can assign a non-optional to an optional, with =, in the sink closure.

I don't follow, are you trying to do something like this?

import SwiftUI
import Combine

class Test: ObservableObject {
    @Published var a: Int = 3

    func test() {
        CurrentValueSubject(4)
            .map { $0 }
            .assign(to: $a)
    }
}

Assuming you have Xcode Beta (since the assign you linked is in beta).

Like that, only where a has an optional property which needs assigning.

I'm not sure the KeyPath can work for properties in this case, as the path is to the publisher, not the property. The regular assign (with the appropriate optional overload) may work.

…but how can we avoid having to store the AnyCancellable manually? That's what I love about this new assign overload, but I don't know how it was implemented. Would it have even been possible to implement without private API?

Sorry, I don’t know how the new one is implemented.

1 Like

Is this maybe what you're looking for? It runs perfectly fine with the new assign(to:):

class Test: ObservableObject {
  @Published var a: Int? = 3
  let subject = CurrentValueSubject<Int, Never>(4)

  func test() {
    subject
      .map({ $0 })
      .assign(to: $a)
  }
}

let object = Test()
object.$a.sink(receiveValue: { value in
  print(value) // Optional(3), Optional(4)
})
object.test()

See my second post — it needs to dig into properties in order to be incredibly useful.

Ah, right. The method isn't designed for that. It's to assign a new value to an @Published property, not to mutate a property of an @Published property. (unless that property itself is an @Published property).

class PublishedObject {
  @Published var a: Int? = 3
}

class Test: ObservableObject {
  @Published var container = PublishedObject()
  let subject = CurrentValueSubject<Int, Never>(4)

  func test() {
    subject
      .map({ $0 })
      .assign(to: container.$a)
  }
}

let object = Test()
object.container.$a.sink(receiveValue: { value in
  print(value) // Optional(3), Optional(4)
})
object.test()

Note that container doesn't have to be @Published. The only requirement here is that the target property is @Published.

The assign(to:) method connects its subscription lifecycle to that of the target publisher somehow. I don't think you need private APIs for that and it might be possible to build something yourself where you connect the subscription lifecycle to the lifecycle of the target property somehow.