Reputation: 578
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
Reputation: 58093
RealityKit v2.0 | March 30, 2023.
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.
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.
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