Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Combine know an ObservableObject actually changed

Tags:

swift

combine

The ObservableObject protocol defined by the Combine framework has an objectWillChange publisher property that lets you know when the properties of this object will change, meaning that, if subscribers to this publisher were to read its value when they get this will change event, they will still read the value before it is changed, what I am trying to understand is:

  • How do frameworks like SwiftUI know that the value actually did change (ie: how do they get the new value) when it seems the only event you can subscribe to is the will change one? Is the underlying mechanism a publicly available API in the Combine framework?
  • How to get the actual new value of an ObservableObject after you received the will change event?
like image 556
Pop Flamingo Avatar asked Dec 29 '19 12:12

Pop Flamingo


Video Answer


2 Answers

SwiftUI doesn't know if the ObservableObject actually changed. SwiftUI trusts you. If you tell it the object will change (by making objectWillChange emit an output), then SwiftUI assumes that the object changes, and schedules a display update.

I'm not 100% sure why Apple chose objectWillChange as the signal. At WWDC 2019, the property was in fact didChange, not objectWillChange. They changed it while Xcode 11 was still in beta. Perhaps what they do is grab the object's state when objectWillChange emits, and then later they grab it again when it's time to update the screen, and they somehow compare the values.

As to how to get the object's value after objectWillChange emits, that's up to you. They way SwiftUI does it is by registering a CFRunLoopObserver for the .beforeWaiting activity. When the observer runs, it re-renders any views whose ObservableObjects have signaled.

Note that if you use a @Published property to store the value in your ObservableObject, then you can subscribe to the property's own publisher by using the $ prefix. For example:

class MyObject: ObservableObject {
    @Published var value: Int = 0
}

let object = MyObject()
let ticket = object.$value
    .sink { print("new value: \($0); old value: \(object.value)") }

object.value = 1

The Published wrapper publishes the current value when you subcribe, and then publishes the new value each time it changes. But note that it publishes the new value before storing it, as the example code above demonstrates with the following output:

new value: 0; old value: 0
new value: 1; old value: 0
like image 126
rob mayoff Avatar answered Sep 30 '22 04:09

rob mayoff


What I did was add to each ObservableObject that I needed to know after changes of

public var objectDidChange = ObservableObjectPublisher()

That you can subscribe to and you manually call .send() on after everything has changed.

like image 43
GilroyKilroy Avatar answered Sep 30 '22 04:09

GilroyKilroy