@@ -47,40 +47,66 @@ public struct Attribute<T>: DynamicProperty {
4747 private let name : AttributeName
4848 private let defaultValue : T ?
4949 private let transform : ( LiveViewNativeCore . Attribute ? ) throws -> T
50+
51+ @StateObject private var storage : Storage = . init( )
5052
5153 /// Create an `Attribute` with an ``AttributeDecodable`` type.
5254 public init ( wrappedValue: T ? = nil , _ name: AttributeName ) where T: AttributeDecodable {
53- self . name = name
54- self . defaultValue = wrappedValue
55- self . transform = { try T ( from: $0) }
55+ self . init ( wrappedValue: wrappedValue, name, transform: { try T ( from: $0) } , element: nil )
5656 }
5757
5858 /// Create an `Attribute` with a `transform` function that converts the attribute into the desired type.
5959 public init ( wrappedValue: T ? = nil , _ name: AttributeName , transform: @escaping ( LiveViewNativeCore . Attribute ? ) throws -> T ) {
60- self . name = name
61- self . defaultValue = wrappedValue
62- self . transform = transform
60+ self . init ( wrappedValue: wrappedValue, name, transform: transform, element: nil )
6361 }
6462
6563 init ( wrappedValue: T ? = nil , _ name: AttributeName , element: ElementNode ) where T: AttributeDecodable {
66- self . name = name
67- self . defaultValue = wrappedValue
68- self . transform = { try T ( from: $0) }
69- self . _element = . init( element: element)
64+ self . init ( wrappedValue: wrappedValue, name, transform: { try T ( from: $0) } , element: element)
7065 }
7166
72- init ( wrappedValue: T ? = nil , _ name: AttributeName , transform: @escaping ( LiveViewNativeCore . Attribute ? ) throws -> T , element: ElementNode ) {
67+ init ( wrappedValue: T ? = nil , _ name: AttributeName , transform: @escaping ( LiveViewNativeCore . Attribute ? ) throws -> T , element: ElementNode ? ) {
7368 self . name = name
7469 self . defaultValue = wrappedValue
7570 self . transform = transform
76- self . _element = . init( element: element)
71+ if let element {
72+ self . _element = . init( element: element)
73+ } else {
74+ self . _element = . init( )
75+ }
7776 }
7877
7978 /// Gets the decoded value of the attribute.
8079 ///
8180 /// If attribute decoding fails, this will return the default value the property wrapper was constructed with.
8281 /// If no default value was provided, the app will crash.
8382 public var wrappedValue : T {
83+ // Use the cached value if possible. Otherwise, decode on demand.
84+ guard let value = storage. value else {
85+ return decode ( )
86+ }
87+ return value
88+ }
89+
90+ /// Container that holds the cached decoded attribute value, and the previous attribute for comparison.
91+ private final class Storage : ObservableObject {
92+ var value : T ?
93+ var previousValue : LiveViewNativeCore . Attribute ?
94+ }
95+
96+ /// Before the View's body is produced, check if the attribute needs to be recomputed.
97+ public mutating func update( ) {
98+ let attribute = element. attribute ( named: name)
99+
100+ // Only recompute the attribute if the attribute is different.
101+ guard storage. previousValue != attribute
102+ else { return }
103+
104+ self . storage. value = decode ( )
105+ self . storage. previousValue = attribute
106+ }
107+
108+ /// Decodes the attribute if possible.
109+ private func decode( ) -> T {
84110 do {
85111 return try transform ( element. attribute ( named: name) )
86112 } catch {
0 commit comments