Home > Back-end >  Notifications not sent when changing an ObservedObject
Notifications not sent when changing an ObservedObject

Time:10-21

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?

CodePudding user response:

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`

CodePudding user response:

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
  • Related