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

使用ARKit+RealityKit+Vision的AR视图内存泄漏问题求助

AR视图内存泄漏导致应用崩溃问题

我搭建了一个示例项目,每次关闭HandInteractionARViewContainer124视图时内存都无法释放,进入该视图4-5次后应用就会崩溃。我听说NavigationView存在内存问题,因此改用了最新的NavigationStack实现导航:

struct Nav : View {
    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink(destination: {
                    HandInteractionARViewContainer124().edgesIgnoringSafeArea(.all)
                }, label: {
                    Text("AR View")
                        .padding(50)
                })

                NavigationLink(destination: {
                    HandInteractionARViewContainer124().edgesIgnoringSafeArea(.all)
                }, label: {
                    Text("AR View 2")
                        .padding(50)
                })
            }
        }
    }
}

核心实现部分:加载RC文件,并利用Vision实现手部触碰Box实体时发送通知触发器的功能:

struct HandInteractionARViewContainer124: UIViewControllerRepresentable {

    func makeUIViewController(context: UIViewControllerRepresentableContext<HandInteractionARViewContainer124>) -> HandInteractionARViewController124 {
        let viewController = HandInteractionARViewController124()
        return viewController
    }

    func updateUIViewController(_ uiViewController: HandInteractionARViewController124, context: UIViewControllerRepresentableContext<HandInteractionARViewContainer124>) {

    }

    func makeCoordinator() -> HandInteractionARViewContainer124.Coordinator {
        return Coordinator()
    }

    class Coordinator {

    }
}

class HandInteractionARViewController124: UIViewController, ARSessionDelegate {

    private var arView:ARView!
    let sceneUno = try! K128.loadScene()

    lazy var request:VNRequest = {
        var handPoseRequest = VNDetectHumanHandPoseRequest(completionHandler: handDetectionCompletionHandler)
        handPoseRequest.maximumHandCount = 1
        return handPoseRequest
    }()
    var viewWidth:Int = 0
    var viewHeight:Int = 0
    var box:ModelEntity!

    var boxButton:ModelEntity!

    override func viewDidLoad() {
        super.viewDidLoad()
        arView = ARView(frame: view.bounds)
        arView.session.delegate = self
        view.addSubview(arView)
        let config = ARWorldTrackingConfiguration()
        config.environmentTexturing = .automatic
        config.frameSemantics = [.personSegmentation]
        config.planeDetection = [.horizontal]
        arView.session.run(config, options: [])
        viewWidth = Int(arView.bounds.width)
        viewHeight = Int(arView.bounds.height)
        setupObject()
    }

    private func setupObject(){
        let anchor = AnchorEntity(plane: .horizontal)

        let plane = ModelEntity(mesh: .generatePlane(width: 2, depth: 2), materials: [OcclusionMaterial()])

        anchor.addChild(plane)

        plane.generateCollisionShapes(recursive: false)

        plane.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .static)

        box = ModelEntity(mesh: .generateBox(size: 0.05), materials: [SimpleMaterial(color: .white, isMetallic: true)])
        box.generateCollisionShapes(recursive: false)
        box.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
        box.position = [0,0.025,0]

        anchor.addChild(sceneUno)
        anchor.addChild(box)

        arView.scene.addAnchor(anchor)
    }

    var recentIndexFingerPoint:CGPoint = .zero

    func handDetectionCompletionHandler(request: VNRequest?, error: Error?) {
        guard let observation = request?.results?.first as? VNHumanHandPoseObservation else { return }

        guard let indexFingerTip = try? observation.recognizedPoints(.all)[.indexTip],
              indexFingerTip.confidence > 0.3 else {return}

        let normalizedIndexPoint = VNImagePointForNormalizedPoint(CGPoint(x: indexFingerTip.location.y, y: indexFingerTip.location.x), viewWidth,  viewHeight)

        if let entity = arView.entity(at: normalizedIndexPoint) as? ModelEntity, entity == box {
            entity.addForce([0,40,0], relativeTo: nil)
            sceneUno.notifications.flipFlop.post()
        }
        recentIndexFingerPoint = normalizedIndexPoint
    }

    func session(_ session: ARSession, didUpdate frame: ARFrame) {
        let pixelBuffer = frame.capturedImage
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            let handler = VNImageRequestHandler(cvPixelBuffer:pixelBuffer, orientation: .up, options: [:])
            do {
                try handler.perform([(self?.request)!])
            } catch let error {
                print(error)
            }
        }
    }
}

内存泄漏修复方案

1. 清理ARSession代理与资源

视图销毁时必须移除ARSession代理并停止会话,避免ARSession持有控制器引用:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // 移除代理
    arView.session.delegate = nil
    // 暂停会话
    arView.session.pause()
    // 清理AR场景锚点
    arView.scene.anchors.removeAll()
    // 移除ARView并置空
    arView.removeFromSuperview()
    arView = nil
    // 置空实体引用
    box = nil
}

2. 修复Vision请求的强引用问题

将request改为可选类型,在视图销毁时取消请求,同时安全访问self避免强制解包:

// 修改request为可选
var request: VNRequest?

override func viewDidLoad() {
    super.viewDidLoad()
    // ...其他初始化代码
    // 初始化Vision请求
    request = VNDetectHumanHandPoseRequest(completionHandler: handDetectionCompletionHandler)
    request?.maximumHandCount = 1
}

// 修改session方法中的请求执行逻辑
func session(_ session: ARSession, didUpdate frame: ARFrame) {
    let pixelBuffer = frame.capturedImage
    DispatchQueue.global(qos: .userInitiated).async { [weak self] in
        guard let self = self, let request = self.request else { return }
        let handler = VNImageRequestHandler(cvPixelBuffer:pixelBuffer, orientation: .up, options: [:])
        do {
            try handler.perform([request])
        } catch let error {
            print(error)
        }
    }
}

// 在viewWillDisappear中取消请求
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    request?.cancel()
    request = nil
    // ...其他清理代码
}

3. 移除强制解包风险

将try!改为错误处理,避免加载场景失败直接崩溃,同时确保资源能正确释放:

// 修改sceneUno为可选
var sceneUno: K128?

override func viewDidLoad() {
    super.viewDidLoad()
    do {
        sceneUno = try K128.loadScene()
        setupObject()
    } catch {
        print("场景加载失败:\(error)")
        // 可添加错误提示逻辑
    }
    // ...其他初始化代码
}

// 修改setupObject方法,先检查sceneUno是否存在
private func setupObject(){
    guard let sceneUno = sceneUno else { return }
    let anchor = AnchorEntity(plane: .horizontal)

    let plane = ModelEntity(mesh: .generatePlane(width: 2, depth: 2), materials: [OcclusionMaterial()])
    anchor.addChild(plane)
    plane.generateCollisionShapes(recursive: false)
    plane.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .static)

    box = ModelEntity(mesh: .generateBox(size: 0.05), materials: [SimpleMaterial(color: .white, isMetallic: true)])
    box.generateCollisionShapes(recursive: false)
    box.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
    box.position = [0,0.025,0]

    anchor.addChild(sceneUno)
    anchor.addChild(box)
    arView.scene.addAnchor(anchor)
}

4. 优化SwiftUI视图的生命周期绑定

在HandInteractionARViewContainer124中添加onDisappear回调,确保控制器能及时收到清理通知:

// 修改NavigationLink的destination
NavigationLink(destination: {
    HandInteractionARViewContainer124()
        .edgesIgnoringSafeArea(.all)
        .onDisappear {
            // 可在这里触发额外的清理逻辑,不过核心清理还是在控制器内部处理
        }
}, label: {
    Text("AR View")
        .padding(50)
})

内容的提问来源于stack exchange,提问作者Theuns Botha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 00:07:06