HelloTimo
HelloTimo

Reputation: 578

How to deallocate RealityKit ARView()?

I cannot manage to release my RealityKit ARView() from memory.

I am aware that there are (were?) similar issues with ARKit + SceneKit – with workarounds like this one: https://stackoverflow.com/a/53919730/7826293 which doesen´t solve my problem unfortunately.

The solutions above kind of work by removing everything "suspicious" manually. That is exactly what I did in a even wider scope:

class ViewController_ARViewMemoryTest: UIViewController {

    var arView: ARView?

    init() {
        super.init(nibName: nil, bundle: nil)
        arView = ARView(frame: .zero)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit {
        doEverythingThatsNeeded()
    }

    public func doEverythingThatsNeeded() {
        self.arView?.session.pause()
        self.arView?.session.delegate = nil
        self.arView?.removeFromSuperview()
        // Quite a few more removals and resets here, ie. for Combines AnyCancellables
        self.arView = nil
    }

}

I am calling doEverythingThatsNeeded() from outside as well:

aRViewMemoryTest?.doEverythingThatsNeeded()
aRViewMemoryTest?.arView = nil
aRViewMemoryTest = nil

The issue seems to be independent from the fact that I have wrapped my ARView or alternatively a UIViewController in a SwiftUI UIViewRepresentable / UIViewControllerRepresentable.

I believe it must be a bug and I have filed a report months ago. However I am hoping for workarounds that help until Apple fixes the potential issue.

Thanks a lot!

Upvotes: 6

Views: 2124

Answers (1)

Andy Jazz
Andy Jazz

Reputation: 58093

RealityKit v2.0 | March 30, 2023.

UIKit version

Here's how you can start the deallocation process in UIKit:

import UIKit
import RealityKit

class ViewController: UIViewController {
    
    var arView: ARView? = ARView(frame: .zero)

    deinit { removingView() }
    
    func addingView() {
        self.arView?.frame = .init(x: 0, y: 0, width: 300, height: 700)
        self.view.addSubview(arView!)
    }
    
    func removingView() {
        self.arView?.session.pause()            // there's no session on macOS
        self.arView?.session.delegate = nil     // there's no session on macOS
        self.arView?.scene.anchors.removeAll()
        self.arView?.removeFromSuperview()
        self.arView?.window?.resignKey()
        self.arView = nil
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()            
        addingView()
        
        let boxAnchor = try! Experience.loadBox()
        arView?.scene.anchors.append(boxAnchor)
        
        removingView()
    }
}

The real problem, however, is that when ARView is deinitialized, the memory allocated for it is not fully deallocated (even though the code is 100% correct). Nevertheless, there's good news too - when you use this ARView again, the same allocated memory will be used.

If you need more info, please read this post for details.


SwiftUI version

Since SwiftUI views are values, no deallocation process is needed for them, like in UIKit (I mean deinit methods). However, if we remove ARView from superview, the memory leak will still occur.

enter image description here

import SwiftUI
import RealityKit

struct ContentView : View {
        
    var body: some View {
        NavigationStack {
            ZStack {
                Color.black
                                
                VStack {
                    NavigationLink(destination: RealityKitView()) {
                        Text("Load AR Scene")
                            .foregroundColor(.mint)
                            .font(.title2)
                    }
                }
            }.ignoresSafeArea()
        }
    }
}

Here is ARViewContainer's init(isTapped: Binding<Bool>).

struct RealityKitView : View {
    
    @State private var isTapped: Bool = false
    
    var body: some View {
        ZStack {
            Color.orange.ignoresSafeArea()
            Text("ARView was removed")
                .font(.title3)
            
            ARViewContainer(isTapped: $isTapped)
                .ignoresSafeArea()
            VStack {
                Spacer()
                Button("Remove ARView") {
                    isTapped = true
                }
                .disabled(isTapped ? true : false)
            }
        }
    }
}

And let's implement the method that unlinks the view from its superview.

struct ARViewContainer : UIViewRepresentable {
    
    let arView = ARView(frame: .zero)
    @Binding var isTapped: Bool
    
    func makeUIView(context: Context) -> ARView {
        let model = ModelEntity(mesh: .generateSphere(radius: 0.25))
        let anchor = AnchorEntity()
        model.setParent(anchor)
        arView.scene.anchors.append(anchor)
        return arView
    }
    func updateUIView(_ view: ARView, context: Context) {
        if isTapped {
            view.removeFromSuperview()
        }
    }
}

P. S.

Alas, Apple engineers have not yet solved the ARView's memory leak problem...

Upvotes: 2

Related Questions