@Observable and didSet?

I'm in the process of migrating to the Observation framework but it seems like it is not compatible with didSet. I cannot find information about if this is just not supported or a new approach needs to be implemented?

import Observation

@Observable class MySettings {
    var windowSize: CGSize = .zero
    var isInFullscreen = false
    var scalingMode: ScalingMode = .scaled {
        didSet {
            ...
        }
    }
    
    ...
}

This code triggers this error:

Instance member 'scalingMode' cannot be used on type 'MySettings'; did you mean to use a value of this type instead?

Anyone knows what needs to be done? Thanks!

Post not yet marked as solved Up vote post of Lucky7 Down vote post of Lucky7
2k views

Replies

Do you use a framework as ReactNative ?

Add a Comment

So it is Swift 5.9 ?

It is effectively surprising, as didSet works for Published property in an ObservableObject. So logically (AFAIU) it should work with Observable.

How is ScalingMode defined ? Just an enum ?

However, could you test 2 changes:

  • remove the = .scaled
  • remove the didSet

So didSet is allowed but you're not able to do much with it as you cannot make any reference to self or any of it's members:

Cannot find 'self' in scope; did you mean to use it in a type or extension context?

Instance member 'scalingMode' cannot be used on type 'MySettings'; did you mean to use a value of this type instead?

How is ScalingMode defined ? Just an enum ?

Yes:

enum ScalingMode: Int {
    case scaled
    case actualSize
}

I ran into this too. I tried a few different ways that didn't work. Using combine to observe the values didn't work, but I did some digging and found something that might be viable, although it's a little convoluted.

So @Observable is not just making something conform to ObservableObject, it's its own thing, it's writing some of your code for you. So if you expand the macro (right click on @Observable) it will expand the code it's augmenting your code with. You'll see a bunch of @ObservationTracked macros revealed for your properties. There's some other stuff too, but not specific to your property.

@ObservationTracked
var test: String = ""
...
@ObservationIgnored private var _test: String

If you expand that macro you now see the getter and setter for your property:

@ObservationTracked
var test: String = ""
{
    get {
      access(keyPath: \.test)
      return _test
    }

    set {
      withMutation(keyPath: \.test) {
        _test = newValue
      }
    }
}
...
@ObservationIgnored private var _test: String

Now you have hooks into setting the new value. So you can do something like this:

@ObservationIgnored #Flip this to ignored because you're doing your own manual getter/setter#
var test: String {
    get {
      access(keyPath: \.test)
      return _test
    }

    set {
      withMutation(keyPath: \.test) {
        _test = newValue
        #do your cool thing here#
      }
    }
}
...
@ObservationIgnored private var _test: String = "" #don't forget to move your initial value here#

So with very light testing this seemed to work as expected. YMMV. I think we'll just have to see if this is a pattern, or if Apple can provide us a way to annotate properties we want a didSet hook for. Either way this might be the exact kind of use case we could write our own macro for. But I'd prefer if Apple solves this. I'm going to file a bug with Apple immediately to request they give us a built in solution. If we want to get it fixed, I think Apple needs to hear about and very soon so it still has a chance in getting in this year.

Thanks Chad! Please post your feedback reference so I can file a bug on my end as well!

  • Lucky 7 the number is FB12292225

  • Thank for filing that.

Add a Comment