32 byte NSNumber memory leak - how to fix?

I use the following bit of code to snapshot a View as a UIImage, but it's causing a memory leak:

extension View {
	@ViewBuilder func snapshot(trigger: Bool, onComplete: @escaping (UIImage) -> ()) -> some View {
		self.modifier(SnapshotModifier(trigger: trigger, onComplete: onComplete))
	}
}

fileprivate struct SnapshotModifier: ViewModifier {
	var trigger: Bool
	var onComplete: (UIImage) -> ()

	@State private var view: UIView = .init(frame: .zero)

	func body(content: Content) -> some View {
		content
			.background(ViewExtractor(view: view))
			.compositingGroup()
			.onChange(of: trigger) {
				generateSnapshot()
			}
	}

	private func generateSnapshot() {
		if let superView = view.superview?.superview {
			let render = UIGraphicsImageRenderer(size: superView.bounds.size)
			let image = render.image { _ in
				superView.drawHierarchy(in: superView.bounds, afterScreenUpdates: true)
			}
			onComplete(image)
		}
	}
}

fileprivate struct ViewExtractor: UIViewRepresentable {
	var view: UIView

	func makeUIView(context: Context) -> UIView {
		view.backgroundColor = .clear
		return view
	}

	func updateUIView(_ uiView: UIView, context: Context) {
		// No process
	}
}

Taking the snapshot is triggered like this:

struct ContentView: View {
	@State private var triggerSnapshot: Bool = false
	var body: some View {
	Button("Press to snapshot") {
		triggerSnapshot = true
	}
		TheViewIWantToSnapshot()
			.snapshot(trigger: triggerSnapshot) { image in
				// Save the image; you don't have to do anything here to get the leak.
			}
	}
}

I'm not the best at Instruments, and this is what the Leaks template produces. There are no method names, just memory addresses:

Is this leak in an internal iOS library, is there something wrong with Instruments, or am I missing something obvious in my code?

Thanks.

Answered by DTS Engineer in 866526022

Our engineering teams need to investigate this issue, as resolution may involve changes to Apple's software. Please file a bug report, include a small Xcode project and some directions that can be used to reproduce the problem, and post the Feedback number here once you do. If you post the Feedback number here I'll check the status next time I do a sweep of forums posts where I've suggested bug reports.

Bug Reporting: How and Why? has tips on creating your bug report.

Our engineering teams need to investigate this issue, as resolution may involve changes to Apple's software. Please file a bug report, include a small Xcode project and some directions that can be used to reproduce the problem, and post the Feedback number here once you do. If you post the Feedback number here I'll check the status next time I do a sweep of forums posts where I've suggested bug reports.

Bug Reporting: How and Why? has tips on creating your bug report.

Have you asked Gemini or ChatGPT about this case? When it comes to Swift Concurrency, they are quite good at it. Concerning your issue, Gemini reports to me several issues with the following last comment.

In summary, the most critical issue is the unreliable view hierarchy traversal using view.superview?.superview. You should refactor the ViewExtractor to reliably pass the host UIView back to the modifier.

I've tried to reproduce it in a new project, but it doesn't happen, either in the Simulator or on a physical device, possibly because it only happens when there are a bunch of views or a certain stack, who knows.

Back in the original project, deploying to a physical iPhone and triggering the snapshot function is now producing this in Instruments:

The call to superView.drawHierarchy(in: superView.bounds, afterScreenUpdates: true) within generateSnapshot() is selected. Everything after that is UIKitCore, Foundation or CoreFoundation, so I don't see how I'm leaking the memory.

I've raised FB21074853 with the smaller project. It won't show the issue, but it might be a starting point for the guys at Apple to get to it.

32 byte NSNumber memory leak - how to fix?
 
 
Q