I am a bit lost in Combine at the moment. I have an ObservableObject. This is mostly because this model is used in SwiftUI and having properties @Published means swiftUI can update with them. However now I also have swift code that would like to listen to changes.
class TheModelClass: ObservableObject {
However this code does not compile... And I am a bit surprised?
modelChangeSink = self.model.objectWillChange.sink {
let newValue: PageManagerObservableModel = $0 // This line does not compile
}
With the compile time error being Cannot convert value of type 'ObservableObjectPublisher.Output' to specified type 'PageManagerObservableModel'
which I cant 100% interpret. I am ideally hoping to do things like
if (self.model.property != newValue.property) {
// On property change
}
Should I be using something other than ObservableObject to do this? I cant find a lot of examples of people using ObservableObject outside of SwiftUI so perhaps I should be using something different?
The ObservableObjectPublisher
has an associated type called Output
and that is the type of the values that it publishes. Inside of your model change sink
, that is all the information that the compiler has. That type must trace back to a concrete type, but the compiler doesn't know what that concrete type is. You're telling it to put whatever value that may be into a variable of type PageManagerObservableModel
, but the compiler doesn't have enough information to say that ObservableObjectPublisher.Output
and PageManagerObservableModel
are the same type, so it gives an error.
In all liklihood, TheModelClass.objectWillChange
has an Output
type of Void
(it was in the small sample I tried). This basically means it will invoke subscribers, but not pass them a value.
In normal usage when you want to use ObservableObject you are interested in specific properties of the object so you'd do something like:
import UIKit
class WatchMe: ObservableObject {
@Published var myValue: Int;
init() {
myValue = 10
}
}
let watchable = WatchMe()
let subscription = watchable.$myValue.sink { newValue in
print(newValue)
}
watchable.myValue = 20
watchable.myValue = 30
The value I'm actually interested in knowing about is myValue
. Certainly you could use code like this to communicate between models. You should be aware that the observer is invoked during the willSet
portion of the property being changed. So at the time your subscription is invoked, you get the new value, before it's actually assigned to the ObservedObject
.
Having said that, in my opinion, and as you seem to have noticed, ObservableObject
and the newer variant @Observable
are a publish/subscribe that shines in the context of SwiftUI. But I've had limited success using them outside of that context.
I usually find that one of the other Observer Pattern implementations in the system seem to fit the models I create better. iOS has lots of implementations of the Observer Pattern to choose from. Off the top of my head:
Create your own Combine Subject (CurrentValueSubject
or PassthroughSubject
) and create your sink on those.
Use the delegate pattern to create a dedicated interface for sending change messages.
A variation on the delegate pattern would be to register a function with the model that is invoked each time an interesting value changes.
Use NotificationCenter to broadcast a far reaching notification.
You could also go with Swift Concurrency and use an AsyncSequence
to create a stream of changed values over time.