The answer depends on how we use SwiftUI
. For an app entirely written using it, one might argue that it gets released whenever the app finishes. But what about an UIKit
app that uses some SwiftUI
views?
To answer this question, let’s explore some scenarios involving environment objects. They have reference semantics, and we can track their instances using the memory graph.
If you wish to check the sample project, here’s the repository. It has different git tags
for each scenario explored below.
Scenario 1: holding a View
instance
Let’s begin our exploration by instantiating our RootView
with some environment objects. We won’t attach it to the UI, but only store it in a variable in our view controller:
class MainViewController: UIViewController {
var rootView: AnyView!
override func viewDidLoad() {
super.viewDidLoad()
rootView = AnyView(
RootView()
.environmentObject(EnvA())
.environmentObject(EnvB())
)
}
}
SwiftUI
needs a way to bind these environment objects to a specific view. They will stay around as long as we hold the RootView
instance. If we build the memory graph, here’s what it shows:

Notice the memory graph doesn’t show instances of SwiftUI views. These are structs
, and have value semantics, not living in the heap.
Scenario 2: Holding a reference to a UIHostingController
instance
This is similar to the first scenario. If we hold a reference to a UIHostingController
, the environment will still be around, as the controller holds the value of its root view. Notice this happens even after the SwiftUI
views get removed from the view hierarchy:
@objc private func releaseRootView() {
guard let rootViewHostingController,
let hostingView = rootViewHostingController.view else {
return
}
rootViewHostingController.removeFromParent()
hostingView.removeFromSuperview()
// Scenario 2: Keep holding a reference to the hosting controller.
//self.rootViewHostingController = nil
}
The memory graph continues showing our two instances in memory:

Scenario 3: Having a UIView
with a retain cycle
In this scenario, we explore UIViews
used within a SwiftUI view tree. UIViews have reference semantics, and if an instance leaks in memory, the SwiftUI
environment will continue alive:
final class LeakingUIView: UIView {
private var retainingClosure: (() -> ())!
override func layoutSubviews() {
super.layoutSubviews()
retainingClosure = {
// Scenario 3: Having a retain cycle in a UIView.
self.backgroundColor = .red
}
retainingClosure()
}
}
struct LeakingView: UIViewRepresentable {
func makeUIView(context: Context) -> LeakingUIView {
LeakingUIView()
}
func updateUIView(_ uiView: LeakingUIView, context: Context) {}
}
When we release our hosting controller instance, we notice the environment is still being held due to references associated with the leaking view in memory.

Here’s what the memory graph shows:
LeakingView
has a reference aUITraitCollection
UITraitCollection
has a reference to aSwiftUIEnvironmentWrapper
SwiftUIEnvironmentWrapper
has references to its environment objects (EnvA
,EnvB
)
Scenario 4: Having a retain cycle between a UIView
and an environment object
If we hold a reference to a UIView
in one of the environment objects, and this UIView
was inside a SwiftUI
tree, we have a reference cycle:
struct SubView: UIViewRepresentable {
@EnvironmentObject
private var envA: EnvA
func makeUIView(context: Context) -> SubUIView {
let subView = SubUIView()
// Scenario 4: Retain cycle between environemnt objects and view references.
envA.someView = subView
return subView
}
func updateUIView(_ uiView: SubUIView, context: Context) {}
}
After removing the SwiftUI
views from the screen, here’s what the memory graph shows:

SubUIView
and EnvA
SubUIView
has a reference to aUITraitCollection
UITraitCollection
has a reference to aSwiftUIEnvironmentWrapper
SwiftUIEnvironmentWrapper
has a reference toEnvA
EnvA
has a reference toSubUIView
, and we can go back to the point 1 (a cycle)
Conclusion
Always make sure to:
- Dispose of any
SwiftUI View
values not used anymore - Dispose of any
UIHostingController
references not used anymore - Watch out for memory leaks in:
UIViews
used withinSwiftUI
- references between your
UIViews
and your environment objects UIViewControllers
presenting theUIHostingControllers
- the environment objects themselves
Environment objects can get complex depending on what your views need to accomplish. They might use a lot of computational resources, so it’s necessary to watch out for live instances when the underlying views get deallocated.