如何对相机捕获回调中的CMSampleBuffer进行内存管理?
Hey there! Let's walk through the proper memory management for CMSampleBuffer when you need to use it outside the captureOutput(_:didOutput:from:) callback—this is a super common pitfall with AVFoundation, so great question.
Core Retain/Release Basics
First off, remember that CMSampleBuffer is a Core Foundation object, which means it follows manual memory management rules (ARC doesn't handle it automatically). Apple's docs are clear: if you need to reference this buffer outside the callback's scope, you have to explicitly retain it, then release it when you're done.
Here's how to adjust your code to do this safely:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } // Retain the buffer BEFORE passing it out of this scope CFRetain(sampleBuffer) // Example: Dispatch to a background queue for processing DispatchQueue.global(qos: .userInitiated).async { // Use the sampleBuffer (and its associated imageBuffer) here self.processCapturedBuffer(sampleBuffer) // Release the buffer once you're finished with it CFRelease(sampleBuffer) } }
Critical Rules to Avoid Crashes & Leaks
- Always pair
CFRetainwithCFRelease: Every retain must have a matching release. Skip a release, and you'll leak memory. Release too many times, and your app will crash. - Use
deferfor guaranteed cleanup: If your processing logic has multiple exit points (like early returns on errors), wrap theCFReleasein adeferblock to ensure it runs no matter what. This is a Swift best practice for manual cleanup:DispatchQueue.global(qos: .userInitiated).async { [weak self] in defer { // This runs even if we hit a guard failure below CFRelease(sampleBuffer) } guard let self = self, let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } // Your processing logic here } - Don't hold onto buffers longer than needed:
CMSampleBufferobjects are memory-heavy, especially at high capture resolutions/frame rates. Process them quickly and release immediately to keep your app's memory footprint low.
Performance Best Practices (As Apple Mentions)
To keep your capture pipeline smooth:
- Offload processing to background queues: Never do heavy work (like image conversion, face detection) on the main thread or the capture callback thread. Keep the callback as lightweight as possible—only retain the buffer and dispatch it elsewhere.
- Avoid retaining buffers in bulk: If you need to queue multiple frames for processing, make sure you're releasing them as soon as each is handled. A backlog of retained
CMSampleBuffers can quickly eat up memory. - Know when to copy instead of retain: If you only need the underlying
CVImageBuffer(pixel data) and not the entire sample buffer, you can create a copy of the pixel buffer (usingCVPixelBufferCreateor related methods). This lets you release the originalCMSampleBufferfaster, but it adds a copy cost—weigh this against your performance needs.
Full Safe Example
Here's a complete example that handles async processing, UI updates, and guaranteed cleanup:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } CFRetain(sampleBuffer) DispatchQueue.global(qos: .userInitiated).async { [weak self] in defer { CFRelease(sampleBuffer) } guard let self = self else { return } // Convert CVImageBuffer to UIImage (example processing) let ciImage = CIImage(cvImageBuffer: imageBuffer) guard let cgImage = CIContext(options: [.useSoftwareRenderer: false]).createCGImage(ciImage, from: ciImage.extent) else { return } let capturedImage = UIImage(cgImage: cgImage) // Update UI on main thread DispatchQueue.main.async { self.previewImageView.image = capturedImage } } }
内容的提问来源于stack exchange,提问作者Carpetfizz

