Skip to content

Commit b185bc7

Browse files
Merge pull request #1026 from liveview-native/optimize-view-bodies
Optimize updates with ObservableObject
2 parents a723298 + ad0762b commit b185bc7

File tree

4 files changed

+28
-27
lines changed

4 files changed

+28
-27
lines changed

Sources/LiveViewNative/Property Wrappers/Attribute.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public struct Attribute<T>: DynamicProperty {
9191
private final class Storage: ObservableObject {
9292
var value: T?
9393
var previousValue: LiveViewNativeCore.Attribute?
94+
95+
let objectWillChange = ObjectWillChangePublisher()
9496
}
9597

9698
/// Before the View's body is produced, check if the attribute needs to be recomputed.

Sources/LiveViewNative/Property Wrappers/LiveContext.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public struct LiveContext<R: RootRegistry>: DynamicProperty {
5050
public func buildElement(_ element: ElementNode) -> some View {
5151
// can't use coordinator.builder.fromElement here, as it causes an infinitely recursive type when used with custom attributes
5252
// so use ElementView to break the cycle
53-
return NodeView(node: element.node, context: storage)
53+
return coordinator.builder.fromElement(element, context: storage)
5454
}
5555

5656
/// Builds a view representing the children of the current element in the current context.

Sources/LiveViewNative/Property Wrappers/ObservedElement.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,27 +101,33 @@ extension ObservedElement: DynamicProperty {
101101
let coordinator else {
102102
fatalError("Cannot use @ObservedElement on view that does not have an element and coordinator in the environment")
103103
}
104-
self.observer.update(ref: nodeRef, children: coordinator.document[nodeRef].children().map(\.id), elementChanged: coordinator.elementChanged)
104+
self.observer.update(
105+
ref: nodeRef,
106+
children: self.observer.observeChildren ? coordinator.document[nodeRef].children().map(\.id) : nil,
107+
elementChanged: coordinator.elementChanged
108+
)
105109
}
106110
}
107111

108112
extension ObservedElement {
109113
private class Observer: ObservableObject {
110114
private var cancellable: AnyCancellable?
111-
private var observeChildren = false
115+
fileprivate var observeChildren = false
116+
117+
let objectWillChange = ObjectWillChangePublisher()
112118

113119
init(observeChildren: Bool) {
114120
self.observeChildren = observeChildren
115121
}
116122

117-
fileprivate func update(ref: NodeRef, children: [NodeRef], elementChanged: AnyPublisher<NodeRef, Never>) {
123+
fileprivate func update(ref: NodeRef, children: [NodeRef]?, elementChanged: AnyPublisher<NodeRef, Never>) {
118124
if cancellable == nil {
119125
cancellable = elementChanged
120126
.filter { [weak self] in
121127
if $0 == ref {
122128
return true
123129
} else if self?.observeChildren == true {
124-
return children.contains($0)
130+
return children?.contains($0) ?? false
125131
} else {
126132
return false
127133
}

Sources/LiveViewNative/ViewTree.swift

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct ViewTreeBuilder<R: RootRegistry> {
3030
}
3131

3232
@ViewBuilder
33-
fileprivate func fromNode(_ node: Node, context: LiveContextStorage<R>) -> some View {
33+
func fromNode(_ node: Node, context: LiveContextStorage<R>) -> some View {
3434
switch node.data {
3535
case .root:
3636
fatalError("ViewTreeBuilder.fromNode may not be called with the root node")
@@ -41,7 +41,7 @@ struct ViewTreeBuilder<R: RootRegistry> {
4141
}
4242
}
4343

44-
fileprivate func fromElement(_ element: ElementNode, context: LiveContextStorage<R>) -> some View {
44+
func fromElement(_ element: ElementNode, context: LiveContextStorage<R>) -> some View {
4545
let view = createView(element, context: context)
4646

4747
let modified = view.applyModifiers(R.self)
@@ -210,10 +210,17 @@ private struct ModifierApplicator<Parent: View, R: RootRegistry>: View {
210210
let context: LiveContextStorage<R>
211211

212212
var body: some View {
213-
let remaining = modifiers.dropFirst()
214-
// force-unwrap is okay, this view is never constructed with an empty slice
215-
parent.modifier(modifiers.first!.modifier)
216-
.applyModifiers(remaining, element: element, context: context)
213+
if modifiers.isEmpty {
214+
parent
215+
} else {
216+
ModifierApplicator<_, R>(
217+
// force-unwrap is okay, this view is never constructed with an empty slice
218+
parent: parent.modifier(modifiers.first!.modifier),
219+
modifiers: modifiers.dropFirst(),
220+
element: element,
221+
context: context
222+
)
223+
}
217224
}
218225
}
219226

@@ -245,11 +252,7 @@ extension View {
245252

246253
@ViewBuilder
247254
func applyModifiers<R: RootRegistry>(_ modifiers: ArraySlice<ModifierContainer<R>>, element: ElementNode, context: LiveContextStorage<R>) -> some View {
248-
if modifiers.isEmpty {
249-
self
250-
} else {
251-
ModifierApplicator(parent: self, modifiers: modifiers, element: element, context: context)
252-
}
255+
ModifierApplicator(parent: self, modifiers: modifiers, element: element, context: context)
253256
}
254257

255258
@ViewBuilder
@@ -262,16 +265,6 @@ extension View {
262265
}
263266
}
264267

265-
// not fileprivate because it's used by LiveContext.
266-
internal struct NodeView<R: RootRegistry>: View {
267-
let node: Node
268-
let context: LiveContextStorage<R>
269-
270-
var body: some View {
271-
context.coordinator.builder.fromNode(node, context: context)
272-
}
273-
}
274-
275268
private enum ForEachElement: Identifiable {
276269
case keyed(Node, id: String)
277270
case unkeyed(Node)
@@ -300,7 +293,7 @@ func forEach<R: CustomRegistry>(nodes: some Collection<Node>, context: LiveConte
300293
switch $0 {
301294
case let .keyed(node, _),
302295
let .unkeyed(node):
303-
NodeView(node: node, context: context)
296+
context.coordinator.builder.fromNode(node, context: context)
304297
}
305298
}
306299
}

0 commit comments

Comments
 (0)