One thing caught my curiosity:
How can (real) Combine have a default implementation of ObservableObject.objectWillChange with a stable address? Extensions can't add new stored properties to an instance.
import Combine
class Test: ObservableObject {}
let testObject = Test()
// If `objectWillChange` is a computed property, how is it able to return
// the same object on each call? Where is the underlying storage?
print(ObjectIdentifier(testObject.objectWillChange))
print(ObjectIdentifier(testObject.objectWillChange))
I thought perhaps they're using associated objects, so I tossed in a objc_removeAssociatedObjects(testObject) between those two lines, but it still worked.
That led to me hope into the debugger to see what the raw machine code was doing. Turns out that objectWillChange calls a private reflection API of the Swift standard library (_forEachField(of:options:body)). Notably, this reflection mechanism bypasses the CustomReflectable mechanism, which is why your example doesn't crash with real Combine. They're accessing the Published properties, and storing the ObservableObjectPublisher instance within those property wrappers.
From a performance, I think this make sense. The property wrapper _enclosingInstance approach requires a dynamic type-check of self on every setting of a property. In contrast, this reflection approach just does some pricy reflection once up-front on the construction of an ObservableObject, but the main flow (emitting events when Published properties change) can be simpler.