Generally speaking, objectWillChange is called immediately before the new value is written. Further, the expectation within Combine is that the old value will only be available until the moment the objectWillChange call completes, and not afterwards; the publisher is expected to enforce this. This is one of the reasons for the existence of the @Published property wrapper, and the change from the previous guidance of just using your own 'objectWillChange' subject—the property wrapper can enforce certain behaviors, for instance locking access to its wrapped value from the point it signals the will-change event to the time the item has actually changed. For instance, a simplistic implementation might look something like this:
@propertyWrapper
struct Published {
let publisher: PassthroughSubject<void, never=""> // this is a reference type
init(publisher: PassthroughSubject, wrappedValue: Value<void, never="">) {
self.publisher = publisher)
self._wrappedValue = wrappedValue
}
var lock = SomeLock()
private var _wrappedValue: Value
private var _oldValue: Value!
var wrappedValue: Value {
get {
if lock.tryLock() {
return _wrappedValue
} else {
// we're updating, and the contract is that _oldValue *must* be valid while locked.
return _oldValue
}
}
set {
_oldValue = _wrappedValue
lock.whileLocked {
publisher.send()
_wrappedValue = newValue
}
}
}
}
The actual implementation will be significantly more gnarly than this, and would really need to involve some sort of multi-state condition lock (such as NSConditionLock) that would allow the getter and setter to more efficiently synchronize themselves: so the getter can read when in not-updating state and in will-change state, but not in actually-changing state, etc. (read up on condition locks and multi-threaded queue implementations to find some basic examples).