DynamicPropertyUpdater.swift 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. /// A cache for dynamic property updaters.
  2. ///
  3. /// The keys are the `ObjectIdentifier`s of various `Base` types that we have
  4. /// already computed dynamic property updaters for, and the elements are
  5. /// corresponding cached instances of `DynamicPropertyUpdater<Base>`.
  6. ///
  7. /// From some basic testing, this caching seems to reduce layout times by 5-10%
  8. /// (at the time of implementation).
  9. @MainActor
  10. private var updaterCache: [ObjectIdentifier: Any] = [:]
  11. /// A helper for updating the dynamic properties of a stateful struct (e.g.
  12. /// a struct conforming to ``View`` or ``App``).
  13. ///
  14. /// At initialisation the updater will determine the byte offset of each
  15. /// stateful property in the struct.
  16. struct DynamicPropertyUpdater<Base> {
  17. /// The offsets and types of each of `Base`'s dynamic properties.
  18. private var propertyOffsets: [(offset: Int, type: any DynamicProperty.Type)]
  19. /// Creates a new dynamic property updater which can efficiently update
  20. /// all dynamic properties on any value of type `Base` without creating
  21. /// any mirrors.
  22. ///
  23. /// - Parameters:
  24. /// - base: The base value to update the dynamic properties of.
  25. @MainActor
  26. init(for value: Base) {
  27. self.propertyOffsets = []
  28. // Unlikely shortcut, but worthwhile when we can.
  29. guard MemoryLayout<Base>.size > 0 else { return }
  30. if let cachedUpdater = updaterCache[ObjectIdentifier(Base.self)] {
  31. self = cachedUpdater as! Self
  32. return
  33. }
  34. forEachField(of: value) { _, offset, fieldValue in
  35. if let type = type(of: fieldValue) as? any DynamicProperty.Type {
  36. propertyOffsets.append((offset, type))
  37. }
  38. }
  39. updaterCache[ObjectIdentifier(Base.self)] = self
  40. }
  41. /// Updates each dynamic property of the given value.
  42. func update(_ value: Base, with environment: EnvironmentValues, previousValue: Base?) {
  43. for (offset, type) in propertyOffsets {
  44. update(type)
  45. func update<Property: DynamicProperty>(_: Property.Type) {
  46. getProperty(Property.self, of: value, at: offset).update(
  47. with: environment,
  48. previousValue: previousValue.map {
  49. getProperty(Property.self, of: $0, at: offset)
  50. }
  51. )
  52. }
  53. }
  54. }
  55. }