Skip to content

Instantly share code, notes, and snippets.

@importRyan
Last active June 10, 2021 21:50
Show Gist options
  • Save importRyan/5d20a72583fda73e795720460e80fd2c to your computer and use it in GitHub Desktop.
Save importRyan/5d20a72583fda73e795720460e80fd2c to your computer and use it in GitHub Desktop.
WWDC SwiftUI Question

In D_Gen3.memgraph, Xcode memory debugger reports one leaked type, the Color for the bullseye in an HSV color picker, which adapts to brightness. Instruments would report thousands of leaks, 99% not referring to app code.

% leaks D_Gen3.memgraph

9   com.apple.SwiftUI                     0x1c94e6318 partial apply for closure #1 in ViewBodyAccessor.updateBody(of:changed:) + 44
8   Inclusivity                           0x102dc53e8 protocol witness for View.body.getter in conformance HSVHueSatWheelBullseye + 40  <compiler-generated>:0
7   Inclusivity                           0x102dc3594 HSVHueSatWheelBullseye.body.getter + 820  HSVHueSatWheelBullseye.swift:12
6   com.apple.SwiftUI                     0x1c9e748ac ZStack.init(alignment:content:) + 180
5   Inclusivity                           0x102dc409c closure #1 in HSVHueSatWheelBullseye.body.getter + 960  HSVHueSatWheelBullseye.swift:20
4   Inclusivity                           0x10317f9ec HSVColorPickerVM.targetRingColor.getter + 292  HSVColorPickerVM.swift:198
3   com.apple.SwiftUI                     0x1c98e888c Color.init(hue:saturation:brightness:opacity:) + 80
2   libswiftCore.dylib                    0x1b1e2c9b4 swift_allocObject + 64
1   libswiftCore.dylib                    0x1b1e2c7bc swift_slowAlloc + 64
0   libsystem_malloc.dylib                0x1a4377bc8 _malloc_zone_malloc + 160 
  
1 (48 bytes) ROOT LEAK: <SwiftUI.(ColorBox in $1c9f228c0)<SwiftUI.Color.Resolved> 0x12bb86c80> [48]

This is the getter for that Color in an ObservableObject, whose lifetime and count is exactly as expected.

   var targetRingColor: Color {
        currentSharedColor.val > 0.55
            ? Color(hue: 0,
                    saturation: 0,
                    brightness: min(0.2, 1 - Double(currentSharedColor.val))) // currentSharedColor is a private value type
            : Color(hue: 0,
                    saturation: 0,
                    brightness: max(0.8, 1 - Double(currentSharedColor.val)))
    }

and here it is in a View

               Circle().fill(vm.targetRingColor)
                    .frame(width: vm.hsBullseyeDiameter, height: vm.hsBullseyeDiameter)
                    .transition(AnyTransition.scale.animation(.easeInOut))
                    .animate(.easeIn, value: vm.hideBullseye)
                    .animate(.easeIn, value: vm.hsBullseyeDiameter)

The .animate modifier is just sugar for respecting ReduceMotion. (PS. Please add a zero-effort way to low key or cut off motion by an API like this!)

In E_Gen4.memgraph, Xcode reports the leak in Example 1, plus another leaked type from the same HSV color picker, this time on the saturation overlay.

10  Inclusivity                           0x1032b10bc HSVHueSatWheelRainbow.body.getter + 488  HSVHueSatWheelRainbow.swift:12
9   com.apple.SwiftUI                     0x1c9e748ac ZStack.init(alignment:content:) + 180
8   Inclusivity                           0x1032b16f0 closure #1 in HSVHueSatWheelRainbow.body.getter + 764  HSVHueSatWheelRainbow.swift:18
7   Inclusivity                           0x1032b1d98 HSVHueSatWheelRainbow.saturationEmulation.getter + 192  HSVHueSatWheelRainbow.swift:28
6   Inclusivity                           0x1032b30a0 HSVHueSatWheelRainbow.gradient.getter + 188  HSVHueSatWheelRainbow.swift:33
5   com.apple.SwiftUI                     0x1c98aa588 Gradient.init(colors:) + 220
4   com.apple.SwiftUI                     0x1c92757c4 specialized ContiguousArray._createNewBuffer(bufferIsUnique:minimumCapacity:growForAppend:) + 40
3   com.apple.SwiftUI                     0x1c927635c specialized _ContiguousArrayBuffer._consumeAndCreateNew(bufferIsUnique:minimumCapacity:growForAppend:) + 104
2   libswiftCore.dylib                    0x1b1e2c9b4 swift_allocObject + 64
1   libswiftCore.dylib                    0x1b1e2c7bc swift_slowAlloc + 64
0   libsystem_malloc.dylib                0x1a4377bc8 _malloc_zone_malloc + 160 
====
    2 (128 bytes) ROOT LEAK: <Swift._ContiguousArrayStorage<SwiftUI.Gradient.Stop> 0x11af9d960> [80]
       1 (48 bytes) <SwiftUI.(ColorBox in $1c9f228c0)<SwiftUI.Color.(OpacityColor in $1c9f91124)> 0x11af9d8f0> [48]
    var saturationEmulation: some View {
        Circle()
            .fill(gradient)
            .blendMode(.luminosity)
    }

    var gradient: RadialGradient {
        RadialGradient(gradient: Gradient(colors: [.white, Color.white.opacity(0)]),
                       center: .center,
                       startRadius: 3,
                       endRadius: vm.radius * 0.98)
    }

The vm.radius references the same ObservableObject in Example 1, whose lifetime and count (1) is exactly as expected.

Instruments

Leaks will report ~7,000 leaks immediately upon app start and hundreds more upon navigation steps. A tiny tiny fraction refer back to app code. Maybe 10 - 15.

Allocations will report growth of 500 kB to 1 MB per navigation, so something is being kept that needn't be. -- Navigation is done programmatically by NavigationLink and a calculated Binding that prevents NavigationView from nil-ing out values during a transition -- No VM object in the app is designed to store values that would relate to a navigation action. -- All object counts are as expected.

Similarly, Allocations has very few traces and points to code that doesn't make sense, like a view body in the Sidebar, an updateNSView() closure, a combine pipeline that has no strong references.

Xcode Memory Graph

Sometimes Xcode reports no leaks, other times it reports a few to tens centering mostly around Colors or Gradients.

It's hard for me to reconcile the counts Instruments Leaks produces and what memgraph is saying. Both point to code that doesn't quite make sense to me, indicating I'm missing some obvious or fundamental concepts in SwiftUI or need to improve debugging skills. If the traces are pointing to a problem higher up than the HSV picker, I'm not sure how to figure out more information on the culprit.

I'm hesitant to file a Feedback because at this stage in my learning, it's probably my fault.

Files + Keynote quick highlights

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment