← All lessonsGuide · Inside the Attribute Graph
Guide

Inside the Attribute Graph

What a view becomes once SwiftUI has it: a handful of numbered nodes, edges made by reads, and one zero-byte node per @State. Every number on these pages was read out of a running program.

1 · of 8

The code, the app, the debugger

Start here. This is one view, the app it makes, and a debugger stopped inside SwiftUI while that app runs. The numbers in the comments are attributes: the ids AttributeGraph gave the nodes it built for this view. The pages after this one explain what they are and what happens to them.

Trip.swift · tap a line to find its node
 1  struct Trip: View { 2      // node 800 · your struct, as SwiftUI stores it (96 bytes) 3      @State private var nights = 3        // signal 3032 4      @State private var city = "Lisbon"   // signal 3096 5      @State private var favorite = false  // signal 3124 6   7      // node 3152 · runs body; reads 624, 800, 3032, 3096, 3124 8      var body: some View { 9          VStack(spacing: 16) {10              // 3216 and 3256 read what body returns, then11              // 7 more nodes down to 4456, the root display list12              Text("\(city), \(nights) nights, \(favorite)")13              // a tap ends in AGGraphInvalidateValue(the signal)14              Text("+1 night").onTapGesture { nights += 1 }15              Text("favorite").onTapGesture { favorite.toggle() }16              Text("nights = 3").onTapGesture { nights = 3 }17          }18      }19  }
Attribute graph · tap a node · tap 3152 to show or hide what's below it
external, no rulea @State's signalhas a rule
AttributeRuleValueReadsRead byIts job
800
lines 1–2
none (_External)Trip, 96 bytes: your struct as it was handed in. All three fields are still .thunk with _location: nil.—3152The view's value. When a parent hands over a new Trip, it lands here.
3152
lines 7–8
DynamicBody<ViewBodyAccessor<Trip>>Text, 60 bytes: "Lisbon, 3 nights, false"624, 800, 3032, 3096, 31243216, 3256The view's body. Running its rule calls your body; the value is what body returned.
3032
line 3 · written on 14, 16
none (_External)(), 0 bytes—3152The signal for nights. Invalidating it is how a write reaches the graph.
3096
line 4
none (_External)(), 0 bytes—3152The signal for city
3124
line 5 · written on 15
none (_External)(), 0 bytes—3152The signal for favorite
624
not in your code
none (_External)Phase, 4 bytes: Phase(value: 0)—3152 and 5 moreSwiftUI's own input: a reset counter and an "is being removed" bit.

Where the top row comes from

The five nodes on top have no inputs: nothing in the graph feeds them. They are the graph's inputs, external nodes that SwiftUI writes from outside. In this window there are 26 of them, among them the environment (576), the time (536), the root origin and size (856, 904) and the safe-area insets (1080). Trip is the root view in these dumps, so its struct, 800, is one of those: NSHostingView writes it with the struct it was given, and no node sits above it.

A view inside a parent looks different, and this is the real graph of one. Planner is the root; two Editor children get $nights. The parent's body node, 3096, holds the children's structs inside its value, the VStack's tuple. Each child's body node reads 3096 directly. That is what sits above a nested view: its parent's body node, not a node of its own.

Planner.swift · the parent and two children
struct Planner: View {
    @State private var nights = 3          // signal 3032, read by all three bodies
    var body: some View {                  // node 3096 · its value holds both Editor structs
        VStack {
            Text("\(nights) nights")
            Editor(name: "A", nights: $nights)   // body node 3256 reads 3096
            Editor(name: "B", nights: $nights)   // body node 3336 reads 3096
        }
    }
}
struct Editor: View {
    let name: String
    @Binding var nights: Int
    var body: some View {
        Text("\(name): \(nights)")
    }
}
Attribute graph · tap a node

Below, the app on the left is running. Tap something in it and the debugger on the right stops at each breakpoint in the real call order, with the stack, the variables and the graph's flags at that moment. The six breakpoints that are on are the ones in the Xcode project; switch on the others to stop everywhere.

Trip
Lisbon, 3 nights, false
Breakpoints
    ● Running · tap something in the app
    Call stack
    1. Not stopped.
    Variables
    Not stopped.
    The graph · the signal and the nine nodes below it, with flags
    Console
    Tap something in the app.

    What's real and what's tidied. The stops, their order and the call stacks are the ones lldb recorded; the values are cleaned up for reading. The node numbers and flags are from the dumps of the one-Text Trip on page 3; the three tap targets are the only addition, and the nodes SwiftUI makes for them are left out. Tap gestures are used instead of Buttons because a Button keeps @State of its own and one click writes it five more times.