6

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!

HelloTimo
  • 558
  • 1
  • 5
  • 18

1 Answers1

2

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...

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • Thanks! I am not using an Experience. But otherwise I have stripped down my code to only the AppDelegate and your class, plus a button which sets the viewController to nil: Still no success. – HelloTimo Jan 25 '20 at 14:49
  • Hey @AndyFedo, thanks again. Could you comment on which role the `updateUIView(_ uiView: ARView, context: UIViewRepresentableContext)` method plays in this context? If I start an empty ARView in `makeUIView(context: Context) -> ARView` containing `ARView(frame: somethingNotZero)` I see the memory overhead of about 400MB for the started session. So far so good. No success in getting rid of the memory consumption either... – HelloTimo Jan 27 '20 at 10:50
  • Hi @Andy, did you any success on this? – HelloTimo Feb 09 '20 at 21:19
  • Hi @HelloTimo. Not yet. – Andy Jazz Feb 10 '20 at 05:23
  • I am not quite sure if I am dealing with a framework bug or simply do something wrong. Any input appreciated. – HelloTimo Feb 10 '20 at 09:43
  • 1
    @AndyJazz the SwiftUI version works, but there should be a note that `updateUIView` can be called before `ARViewContainer` needs to be unloaded. Thanks for this answer though as it definitely helped deallocate the container. – Jan-Michael Tressler Dec 15 '21 at 22:27
  • what's the point of doing `uiView.removeFromSuperview()` in the `updateUIView`? it will simply not work since it will remove the ARView after it will create it from the `makeUIView` – XcodeNOOB Sep 14 '22 at 20:05
  • I just demonstrated how easy it is to delete a view in SwiftUI (added `asyncAfter`). – Andy Jazz Sep 14 '22 at 20:35
  • I have this same problem in SwiftUI. I have a button that dismisses the ARView and it also does removeFromSuperview() in updateUIView. And I used your asyncAfter with + 3.0 but the memory is still not being deallocated. Plus, if I come back to opening the ARView again, I get a crash since it was removed from the superview. So the deallocation of memory works for you in SwiftUI? – D. Rothschild Jan 24 '23 at 01:42