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:
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