Search code examples
iosswiftmemory-leaksavfoundationcore-graphics

Memory Leak When Creating Image Buffers During Video Capture in iOS Swift App


I'm developing an iOS app in Swift that captures a video of a rotating view, but I'm facing a memory leak issue. After the video is exported, there's still an excess of 2GB stored in memory, which is not deallocated even after the function completes. This issue persists and crashes the app when attempting to run the function a second time in production.

The issue seems to occur when creating image buffers. Here's the relevant code snippet:

// ViewFrame struct and render method
struct ViewFrame {
    private let image: UIImage?
    private let snapshot: UIView?

    // ... initializers omitted for brevity ...

    func render() -> UIImage {
        if let existingImage = image {
            return existingImage
        } else {
            return snapshot!.asImage(afterScreenUpdates: true)
        }
    }
}

// RecordView function within ViewRecordingSession class
private func recordView() {
    // ... other code omitted for brevity ...

    Timer.scheduledTimer(withTimeInterval: 1 / framesPerSecond, repeats: true) { [weak self] timer in
        guard let strongSelf = self else { return }
        
        if !strongSelf.isRecording {
            timer.invalidate()
            uiView.removeFromSuperview()
        } else {
            if strongSelf.useSnapshots, let snapshotView = uiView.snapshotView(afterScreenUpdates: false) {
                strongSelf.frames.append(ViewFrame(snapshot: snapshotView))
            } else {
                strongSelf.frames.append(ViewFrame(image: uiView.asImage(afterScreenUpdates: false)))
            }
            
            // ... other code omitted for brevity ...
        }
    }
}

// GenerateAsset function within ViewRecordingSession class
private func generateAsset() {
    assetGenerationCancellable?.cancel()
    let frameImages = frames.map { $0.render() }
    
    // ... other code omitted for brevity ...
}

Using the Memory Graph Debugger, I've identified that the CG raster data is not being deallocated. The specific function that's contributing to the leak is UIView.asImage(afterScreenUpdates:).

The Memory Graph indicates a strong reference to the views or images captured is preventing deallocation. How can I ensure that these objects are released correctly after they are used?


Solution

  • I think it's good to check out a few parts

    1. Check Whether ViewFrame's snapshot is being used or not

    ex)

    // AS-IS
        func render() -> UIImage {
            if let existingImage = image {
                return existingImage
            } else {
                return snapshot!.asImage(afterScreenUpdates: true)
            }
        }
    
    // TO-BE
    func render() -> UIImage? {
        if let existingImage = image {
            return existingImage
        } else if let snapshot = snapshot {
            let renderedImage = snapshot.asImage(afterScreenUpdates: true)
    
            // If using snapshot before, remove from super view
            snapshot.removeFromSuperview()
            return renderedImage
        }
        return nil
    }
    
    

    2. Check timer.invalidate()

    You use [weak self] in Timer.scheduledTimer's action block. But timer.invalidate() is not called in your's code

    3. Check ViewRecordingSession's frames

    After you append viewFrame in frames, you may be not remove viewFram in frames


    I hope it helps your situation. have a nice day~