Skip to content

Commit d0c4ec3

Browse files
authored
Add backing storage and caching for @Attribute (#997)
* Add backing storage and caching for `@Attribute` * Remove print
1 parent 9239810 commit d0c4ec3

File tree

2 files changed

+48
-23
lines changed

2 files changed

+48
-23
lines changed

Sources/LiveViewNative/Property Wrappers/Attribute.swift

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {

Sources/LiveViewNative/ViewTree.swift

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,19 +201,18 @@ private struct ModifierObserver<Parent: View, R: RootRegistry>: View {
201201
let parent: Parent
202202
@ObservedElement private var element
203203
@LiveContext<R> private var context
204+
@Attribute("modifiers", transform: { attribute in
205+
guard let encoded = attribute?.value else { return [] }
206+
207+
let decoder = makeJSONDecoder()
208+
209+
guard let decoded = try? decoder.decode([ModifierContainer<R>].self, from: Data(encoded.utf8))
210+
else { return [] }
211+
212+
return decoded
213+
}) private var modifiers: [ModifierContainer<R>]
204214

205215
var body: some View {
206-
let modifiers: [ModifierContainer<R>]
207-
if let encoded = element.attributeValue(for: "modifiers") {
208-
let decoder = makeJSONDecoder()
209-
if let decoded = try? decoder.decode([ModifierContainer<R>].self, from: Data(encoded.utf8)) {
210-
modifiers = decoded
211-
} else {
212-
modifiers = []
213-
}
214-
} else {
215-
modifiers = []
216-
}
217216
return parent
218217
.applyModifiers(modifiers[...], element: element, context: context.storage)
219218
}

0 commit comments

Comments
 (0)