iOS Swift应用视频采集时创建图像缓冲区引发内存泄漏问题
问题
我正在开发一款Swift语言的iOS应用,用于采集旋转视图的视频,但遇到了内存泄漏问题。视频导出后,仍有超过2GB的内存未被释放,即便函数执行完成也是如此。该问题会导致生产环境中二次运行此功能时应用崩溃。
问题似乎出在创建图像缓冲区的环节,相关代码片段如下:
// 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 ... }
通过Memory Graph Debugger,我发现CG光栅数据未被释放,导致泄漏的具体函数为UIView.asImage(afterScreenUpdates:)。内存图显示,对采集的视图或图像的强引用阻碍了对象释放,请问如何确保这些对象在使用后能被正确释放?
解决方案
1. 主动清空帧数据引用
视频导出完成后,frames数组会持续持有所有ViewFrame对象(包括其中的图像或视图快照),导致内存无法释放。在generateAsset执行完导出逻辑后,立即清空数组:
private func generateAsset() { assetGenerationCancellable?.cancel() let frameImages = frames.map { $0.render() } // ... 执行视频导出逻辑 ... // 导出完成后立即释放所有帧引用 frames.removeAll() }
2. 重构ViewFrame避免冗余持有
当前ViewFrame同时持有图像和视图的可选值,而视图对象本身会占用大量内存(包含图层、渲染数据等)。可以改成仅存储图像生成逻辑,或直接存储图像而非视图:
struct ViewFrame { private let imageProvider: () -> UIImage // 传入视图时,仅保留图像生成闭包,不持有视图本身 init(snapshotView: UIView) { self.imageProvider = { snapshotView.asImage(afterScreenUpdates: true) } } init(image: UIImage) { self.imageProvider = { image } } func render() -> UIImage { imageProvider() } }
注意:如果采用闭包方式,需确保视图在闭包执行时仍处于有效状态;更稳妥的方式是录制时直接生成图像存入ViewFrame,彻底避免持有视图。
3. 修复asImage方法的内存泄漏
如果UIView.asImage是自定义扩展,检查实现是否存在内存泄漏。推荐使用UIGraphicsImageRenderer替代旧的UIGraphicsBeginImageContext系列API,后者容易因遗漏上下文关闭操作导致泄漏:
extension UIView { func asImage(afterScreenUpdates: Bool) -> UIImage { if afterScreenUpdates { layoutIfNeeded() } let renderer = UIGraphicsImageRenderer(bounds: bounds) return renderer.image { context in layer.render(in: context.cgContext) } } }
4. 确保Timer及时销毁
虽然代码使用了[weak self],但如果isRecording状态切换不及时,Timer可能间接持有会话对象。在会话的deinit方法中主动销毁Timer:
class ViewRecordingSession { private var recordingTimer: Timer? private func recordView() { recordingTimer = Timer.scheduledTimer(withTimeInterval: 1 / framesPerSecond, repeats: true) { [weak self] timer in // ... 原有逻辑 ... } } deinit { recordingTimer?.invalidate() } }
5. 分批处理帧数据(可选)
如果录制时长较长、帧率较高,一次性存储所有帧会瞬间占用大量内存。可以改成每生成一定数量的帧就写入临时文件,导出时再从文件读取,从根源降低内存占用。
内容的提问来源于stack exchange,提问作者harry

