We are currently developing some kind of bridging for an app for which we want older data types written in Obj-C to be available and fully interoperable in Swift / SwiftUI. In particular, we want to implement two-way binding between KVO & SwiftUI.
To achieve that, we made a "bridge" class in Swift for the Obj-C class (the reference) that defines a number of properties that "map" the properties of the reference. On that bridge, we use a custom property wrapper whose role is both 1. the setup of KVO observers that will trigger the Combine/SwiftUI ones, 2. the handling of the get/set accessors to the Obj-C backing.
It works pretty well, except that we randomly get "Exclusive Memory Access" run-time error when a setter of the bridge is invoked from SwiftUI (also, why is it rare & random, should it be systematic?). Here's the cycle:
set (bridge) -> set (Obj-C) -> KVO willChange -> observer (wrapper) -> objectWillChange.send() -> SwiftUI update -> read (bridge)
Basically telling that a read is occurring while a write is in progress. (UI properties, all is on the same thread: main)
We tried a number of different things to move the
objectWillChange.send() outside of the "set (bridge)", but couldn't escape it.
The ObservableObject / @Published combo seems to magically do just fine although it is apparently confronted to the same constraints (objectWillChange.send() within a setter), and we'd like to replicate this in our own wrapper.
As a side note, the actual storage of the property is happening outside of the @propertyWrapper (memory, I mean) in the Obj-C object, and we noticed that using a "nonmutating set" wrappedValue on the wrapper would prevent the "Exclusive Memory Access" run-time error. [Is that OK in this context?]
I don't know whether this is the expected solution here. But it feels like this problem could also impact code that want to use custom wrappers (not @Published) for custom storage for instance, invoking manually the objectWillChange.send() code. Is the run-time error too aggressive, or are more elaborate constructs needed to avoid it from firing?
Additional context: we also implemented a regular Combine publisher in the wrapper in addition to the objectWillChange one in case it is needed.
If someone could shed some light here on what happens & how we are supposed to do it right. Thanks!