0

I have the following code:

class Stuff {
    var str: String?
    var num = 0
}

class MyStuff:ObservableObject {
    @Published var stuff:Stuff?
    @Published var numer: Int?
}

class DoSomething {
    let observedObject = MyStuff()
    var cancellableBag = Set<AnyCancellable>()
    
    init() {
        observedObject.objectWillChange.sink { value in
            print(value)
            print("Object has changed")
        }.store(in: &cancellableBag)
        observedObject.$stuff.sink { value in
            print(value?.str ?? "")
        }.store(in: &cancellableBag )
    }
}

But when I execute:

let doSomething = DoSomething()
doSomething.observedObject.stuff?.str = "Doing something"
doSomething.observedObject.stuff?.num = 2

, the notifications never trigger:

enter image description here

Any of you knows why the notifications never trigger? Or how can I make it happen?

Cristik
  • 30,989
  • 25
  • 91
  • 127
user2924482
  • 8,380
  • 23
  • 89
  • 173
  • 1
    Change your `Stuff` from `class` to `struct`. – OOPer Oct 19 '21 at 16:18
  • @OOPer Why does that matter? Is it because class instance mutation is not setting the reference? – matt Oct 19 '21 at 16:28
  • 1
    @matt The value published in `MyStuff` in the `stuff` property is a pointer to an object. The `@ObservableObject` will only fire a notification if that pointer is replaced. The properties of the objects behind that pointer are not observed, only the pointer value itself. – Scott Thompson Oct 19 '21 at 16:35

2 Answers2

1

As already suggested in the comments, you need to convert your class to a struct if you want to make it work: struct Stuff {. A struct is a value type, which makes it work well with ObservableObject, while a class is a reference type, which, well, doesn't work that well.

@Published properties need to be "pure" value types, if you want to be notified when something in their contents change. By "pure" I mean value types that are made of only of other value types, which are also "pure".

Why this is needed? It's because the @Published mechanism relies on the willSet/didSet property observers, and the property observers are executed only when the value they hold change.

With value types, any change in their structure is propagated upstream, resulting in the whole property being updated. This happens due to the semantics of the value types.

See this code snippet:

struct Question {
    var title: String = ""
}

class Page {
    var question = Question() {
        didSet {
            print("Question changed: \(question)")
        }
    }
}

let page = Page()
page.question.title = "@Published doesn't work"

Any change to the question members results in the whole property being replaces, this triggering the willSet/didSet property observers, so you can nicely monitor any changes in the data structure of question.

The above is, however, not true for classes. Classes have identity, they have a fixed memory location, unlike value types, which get copied all over the place when used. This means that changes in their internal structure are not reflected upstream, as the class instance storage doesn't change.

The only time the @Published observer is triggered for classes, is when you replace the object itself. Try this, and you'll see the notifications are fired:

doSomething.observedObject.stuff = Stuff() // this will print `Object has changed`
Cristik
  • 30,989
  • 25
  • 91
  • 127
-4

The issue was the doSomething.observedObject.stuff it was nil.

I fixed:

let doSomething = DoSomething()
let stuff = Stuff()
stuff.str = "Doig Something"
doSomething.observedObject.stuff = stuff
matt
  • 515,959
  • 87
  • 875
  • 1,141
user2924482
  • 8,380
  • 23
  • 89
  • 173
  • No, you've missed the point, because if you _now_ say `doSomething.observedObject.stuff?.str = "Doing something else"` you _still_ won't get a "notification". – matt Oct 19 '21 at 16:48
  • @matt you are right. Do you know how can fix this? – user2924482 Oct 19 '21 at 16:59
  • Did you read the comments? You've been told the answer twice. – matt Oct 19 '21 at 17:05
  • I get it is because class instance mutation is not setting the reference. But my question is how can make a reference to str inside of Stuff ? @matt – user2924482 Oct 19 '21 at 17:53