Custom Operator Assignment in Initialization

Hey, everyone. I have a wrapper type Observable<Value> wherein the wrapped value is kept in a base property, very similar to the AnyHashable wrapper type. A custom set of operators beginning with "^^" are defined as well and the base value can be set using a custom operator ^^=.

My question is: Is there a way to use the regular value assignment syntax shown below where the type is declared explicitly on the left-hand side?

// Desired (Not Working)
var selectionContactA: ObservableReference<ItemSelectionContact> ^^= []

// Existing (Working)
var selectionContactB = ObservableReference<ItemSelectionContact>([])

As an aside, the following works as expected with this operator after initialization.

var selectionContact = ObservableReference<ItemSelectionContact>([])
selectionContact ^^= [.top, .bottom]

Hi @Freytag and welcome to the community!

You mentioned "wrapper type" and "wrapped value". A property wrapper seems the right tool in this case:

@propertyWrapper class Observable<Value> {
  let base: Value

  var wrappedValue: Value {
    get { /* your getter */ }
    set { /* your setter */ }
  }
  
  init(wrappedValue: Value) {
    self.base = wrappedValue
  }
}

This will let you annotate your selectionContact property with @Observable. Initialization and assignments will work as expected:

@Observable var selectionContact: ItemSelectionContact = []
selectionContact = [.top, .bottom]
1 Like

Thanks for taking the time! This seems to be what I've been looking for this entire time — my reference was a years old project that implemented an Observable<T> as a struct, though all of my observable values are properties and never "passed around" after their creation, property wrappers weren't yet part of Swift back then, I'm quite sure.

All this is supposed to be a replacement for Objective C Key-Value Observation which is a pain to use with structs or enum values (or essentially any value type) and I'm hesitant to dive into the possible performance impact of using dynamic @objcMembers everywhere on top of that.

@xAlien95 I have a bonus question, following your recommendation. If I now have convenience direct access to the wrapped base value (wrappedValue) inside, how can I now access the properties of the actual wrapper?

For instance, my type Observable has subscriptions, e.g. properties willSet and didSet that can be invoked to register a subscription as:

let state = Observable<Bool>(true)
state.didSet.add {
    // Block invoked when state changes.
}

If I now have this state example as a property in a struct or class, how can I register or even remove subscriptions later on?


Answer: Inside the type using the @Wrapper value for its properties, the compiler generates a corresponding private property _value that exposes the wrapper type instead of the wrapped value type.

It is true that an underscore-prefixed property is generated along side the wrapped one, but that property has private access, so you can only access it within your type and not externally.
For more general use cases, you can use a projected value (more infos here). For your uses case in particular, if you really need to expose the entire backing wrapper, you can set the projected value as self:

@propertyWrapper class Observable<Value> {
  var wrappedValue: Value { get set ... }
  var projectedValue: Observable<Value> { self }

  ...
}

If your property wrapper has a projectedValue property, a third property with a leading dollar sign will be generated (in this case $state):

struct S {
  @Observable var state: Bool = true
}

var s = S()
s.$state.didSet.add { ... }
2 Likes

Perfect addition, I was asking myself exactly that.

The private access for these in the case of the project I was coming from turned out to be sufficient — no outside module deals with these values. However, if I wanted to use subclasses with overrides or have protocols access these in one way or another, projectedValue is the answer to exactly that.