/// A cache for dynamic property updaters.
///
/// The keys are the `ObjectIdentifier`s of various `Base` types that we have
/// already computed dynamic property updaters for, and the elements are
/// corresponding cached instances of `DynamicPropertyUpdater`.
///
/// From some basic testing, this caching seems to reduce layout times by 5-10%
/// (at the time of implementation).
@MainActor
private var updaterCache: [ObjectIdentifier: Any] = [:]
/// A helper for updating the dynamic properties of a stateful struct (e.g.
/// a struct conforming to ``View`` or ``App``).
///
/// At initialisation the updater will determine the byte offset of each
/// stateful property in the struct.
struct DynamicPropertyUpdater {
/// The offsets and types of each of `Base`'s dynamic properties.
private var propertyOffsets: [(offset: Int, type: any DynamicProperty.Type)]
/// Creates a new dynamic property updater which can efficiently update
/// all dynamic properties on any value of type `Base` without creating
/// any mirrors.
///
/// - Parameters:
/// - base: The base value to update the dynamic properties of.
@MainActor
init(for value: Base) {
self.propertyOffsets = []
// Unlikely shortcut, but worthwhile when we can.
guard MemoryLayout.size > 0 else { return }
if let cachedUpdater = updaterCache[ObjectIdentifier(Base.self)] {
self = cachedUpdater as! Self
return
}
forEachField(of: value) { _, offset, fieldValue in
if let type = type(of: fieldValue) as? any DynamicProperty.Type {
propertyOffsets.append((offset, type))
}
}
updaterCache[ObjectIdentifier(Base.self)] = self
}
/// Updates each dynamic property of the given value.
func update(_ value: Base, with environment: EnvironmentValues, previousValue: Base?) {
for (offset, type) in propertyOffsets {
update(type)
func update(_: Property.Type) {
getProperty(Property.self, of: value, at: offset).update(
with: environment,
previousValue: previousValue.map {
getProperty(Property.self, of: $0, at: offset)
}
)
}
}
}
}