Expand description
@Observable from Rust — a model SwiftUI re-renders on.
The last thing on SwiftUI’s surface this crate could not build, and the
reason it looked impossible was wrong. The obvious blockers are not blockers:
Observation.Observable is a marker protocol with zero requirements, and
ObservationRegistrar and withObservationTracking are fully exported. What
stood in the way was in the middle — an @Observable model is a class, and
this crate mints structs.
It does not have to be a Swift class. Observable requires AnyObject, and
an NSObject subclass is one; crate::objc::ClassBuilder mints those. The
only piece missing was a conformance record whose type reference is a class
rather than a nominal descriptor — IndirectObjCClass, one of the four kinds
the format already has, which crate::conformance::Conformer now emits.
let model = observable::model(0);
let watched = model.clone();
// Whatever the block reads is what the handler is told about.
observable::track(move || { watched.get(); }, || println!("it changed"));
model.set(1);§What an @Observable property actually is
Three calls around ordinary storage, which the macro writes for you:
get { registrar.access(self, keyPath: \.value); return storage }
set { registrar.willSet(self, keyPath: \.value)
storage = newValue
registrar.didSet(self, keyPath: \.value) }Property::get and Property::set are that, with the storage on the Rust
side because a class minted through the Objective-C runtime has no ivars.
§Several properties
A model has as many named properties as it is asked for, each its own type:
let model = observable::model(0);
let name = model.property("name", "Ada".to_string());
let ratio = model.property("ratio", 0.5f64);What makes them independent is that the key path is an identity — what
access records and didSet matches against — so each property needs one of
its own, and a view that read only name is not rebuilt when ratio changes.
Key paths are per name, not per model: \.name is one key path in Swift too,
and the registrar keys its observations by (subject, keyPath), so two models
with a name property still change independently.
Structs§
- Model
- An observable model: a class instance, a registrar, and its named properties.
- Property
- One named property of a
Model: storage, and the key path that names it.
Functions§
- environment_
model .environment(model)— put the model where every view below can read it.- focused_
value_ model .focusedValue(model)— publish the model for the commands above it.- model
- A new observable model, with one
Intproperty holdinginitial. - object
- A new observable model with no properties yet.
- track
- Run
read, remembering every observable property it touches, and callon_changeonce when any of them next changes.