You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 14:20:20