Troubleshooting memory leak / retain cycle in Apple's camera app tutorial

4 days ago 10
ARTICLE AD BOX

I'm following along this camera tutorial from Apple: https://developer.apple.com/tutorials/sample-apps/capturingphotos-camerapreview

I have a DataModel:

final class DataModel: ObservableObject { let camera = Camera() @Published var frame: Image? var isPhotosLoaded = false init() { print("DataModel init") Task { await handleCameraPreviews() } } deinit { print("DataModel deinit") } func handleCameraPreviews() async { let imageStream = camera.previewStream .map { $0.image } for await image in imageStream { Task { @MainActor in // CIFilters to come... frame = image } } } }

And the Camera:

class Camera: NSObject { ... deinit() { print("Camera > deinit") } private var addToPreviewStream: ((CIImage) -> Void)? lazy var previewStream: AsyncStream<CIImage> = { AsyncStream { continuation in addToPreviewStream = { ciImage in if !self.isPreviewPaused { continuation.yield(ciImage) } } } }() ... } extension Camera: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let pixelBuffer = sampleBuffer.imageBuffer else { return } addToPreviewStream?(CIImage(cvPixelBuffer: pixelBuffer)) } }

This works well, until I noticed the memory usage climbing every time I switch to a 2nd view and back to the camera again.

Every time I navigate to the 2nd view, I never see "deinit" printed from either the DataModel() or the Camera(). And when I navigate back to the camera view, I see the both the DataModel() and the Camera() print "init".

I admit I understand very little about memory management, but I suspect there is a strong retain cycle.

When I comment out the handleCameraPreviews() function, the problem goes away. I suspect that's where the retain cycle or some other leak is coming from because both objects init and deinit as expected.

I tried updating the handleCameraPreviews() to add [weak self]:

func handleCameraPreviews() async { let stream = camera.previewStream for await frame in stream { try Task.checkCancellation() await MainActor.run { [weak self] in guard let self else { return } self.frame = frame.image } } }

But it didn't help. Unfortunately, this is where I'm out of ideas due to limits of my knowledge. Any suggestions?

Thank you!

Read Entire Article