How does SwiftUI invalidate its dependency graph? #1016
tgrapperon
started this conversation in
General
Replies: 1 comment 6 replies
-
@tgrapperon my guess it that one of the inputs taken into consideration when determining if a view needs to be recalculated is the body's captured values. While experimenting I created this helper: struct Closure {
var ptr: UnsafeRawPointer
var capturePtr: UnsafeRawPointer?
}
extension View {
func debugCapturedValues() -> some View {
let closure = withUnsafePointer(to: self.body) {
$0.withMemoryRebound(to: Closure.self, capacity: 1, \.pointee)
}
print(
"\(Self.self).body: \(closure.ptr) captured: \(closure.capturePtr.map { "\($0)" } ?? "none")"
)
return self
}
} Using the counter example: class Counter: ObservableObject {
@Published var count: Int = 0
}
struct CounterView: View {
@ObservedObject var counter = Counter()
var body: some View {
Stepper(onIncrement: { self.counter.count += 1 }, onDecrement: { self.counter.count -= 1 }) {
Text("\(self.counter.count)")
}
.debugCapturedValues()
}
} Interacting with the stepper results in the following:
Doing the same using struct WithCounter<Content: View>: View {
@StateObject var counter = Counter()
@ViewBuilder var content: (Counter) -> Content
var body: some View { self.content(self.counter) }
}
struct WithCounterView: View {
var body: some View {
WithCounter { counter in
Stepper(onIncrement: { counter.count += 1 }, onDecrement: { counter.count -= 1 }) {
Text("\(counter.count)")
}
.debugCapturedValues()
}
}
} results in the following:
Notice how the captured address doesn't change. In this case, that address points to the counter inside I'm not sure if this is what SwiftUI does under the hood it's more to demonstrate that it's a possibility. |
Beta Was this translation helpful? Give feedback.
6 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
In discussion #1012 and PR #1015, we refined our understanding of the way SwiftUI invalidates its dependency graph, that is, how it detects that things have changed and some
View
deserves to run its body again. It does so by inspecting if some view's stored properties have changed between two updates, most likely by comparing the memory representation of each stored value.I understand how it can happen for plain old values like structs, but I don't know how it works for closures that are stored and lazily run in body. Experience suggests that SwiftUI is able to compare the memory representation of each value captured by the closure, and decides that if they coincide, the result will be the same and the dependency graph should be preserved at this node. This is under this assumption that #1015 was implemented.
I don't know how it can happen (or if it happens at all), nor if this is some Swift feature rather than something specific to SwiftUI.
Does anyone have an idea how it works under the hood?
I'm only asking by curiosity.
Beta Was this translation helpful? Give feedback.
All reactions