"Swift closure context (unknown layout)" memory leak with SwiftUI

I'm introducing some SwiftUI into a primarily UIKit app and have noticed that, as soon as a SwiftUI view is presented / pushed to the nav stack in its hosting view controller, the memory graph shows a number of Swift closure context (unknown layout) entries under an <unknown> header. These entries persist after the SwiftUI view has been popped from the navigation stack.

Is this cause for concern?

I don't think it represents a memory leak within my app, because the entries are grouped under <unknown> as opposed to my app name. But I'm not entirely sure why they exist (and crucially, why they persist).

It's possible to observe this behaviour with a really simple demo app. Just push the following view:

struct ViewController02: View {
    var body: some View {
        Text("Hello, world!")
    }
}

onto a navigation stack:

self.navigationController?.pushViewController(UIHostingController(rootView: ViewController02()), animated: true)

Any advice / guidance / reassurance would be much appreciated. There can be a considerable number of these entries but I can't see how I could be causing a retain cycle.

Replies

Based on the code you included here I don't think there are bugs in your code causing a leak, but we'd have to reproduce the issue to be sure. Can you file a bug following the instructions at https://developer.apple.com/bug-reporting/ including the Xcode and iOS/iPadOS versions you're using, and ideally a small project which reproduces the issue?

With respect to Swift closure contexts and them being listed under <unknown> in the Xcode memory graph debugger - the memory graph debugger groups allocations by the binary which defined them. This works well for most Swift and Objective-C types you create, where types you wrote will be listed under your app's binary. But you use more than just those types - the Swift runtime and CoreFoundation/Foundation libraries provide many types you use directly and indirectly as well. It's possible to create leaks which involve these system-provided types (though, again, I don't think that's happening here).

Swift closure contexts, which hold captures of Swift closures, aren't associated with any particular binary because they're created and managed by the Swift runtime. The memory graph debugger doesn't know which binary was responsible for creating a Swift closure context, so it associates them with <unknown> instead of a specific binary.

The best way to determine what created a closure context is to enable MallocStackLogging, which can show the allocation backtrace of a closure context (or any other allocation). You can enable this in Xcode's Scheme settings using Scheme -> Edit Scheme -> Diagnostics -> MallocStackLogging and selecting "Live Allocations Only". When MallocStackLogging is enabled, the Xcode memory debugger displays the allocation backtrace for the selected allocation.

Add a Comment