I'm having the following view hierarchy which has a retain cycle, that's the simplest I could make to reproduce the issue. All viewmodels and properties has to stay as they are needed in the original solution:
import SwiftUI
struct MainView: View {
@StateObject var viewModel = MainViewModel()
var body: some View {
NavigationView { [weak viewModel] in
VStack {
Button("StartCooking") {
viewModel?.show()
}
if viewModel?.isShowingContainerView == true {
ContainerView()
}
Button("StopCooking") {
viewModel?.hide()
}
}
}
.navigationViewStyle(.stack)
}
}
final class MainViewModel: ObservableObject {
@Published var isShowingContainerView = false
func show() {
isShowingContainerView = true
}
func hide() {
isShowingContainerView = false
}
}
struct ContainerView: View {
@Namespace var namespace
var body: some View {
VStack {
SubView(
namespace: namespace
)
}
}
}
struct SubView: View {
@StateObject var viewModel = SubViewModel()
var namespace: Namespace.ID
var body: some View {
Text("5 min")
.matchedGeometryEffect(id: UUID().uuidString, in: namespace)
.onTapGesture {
foo()
}
}
private func foo() {}
}
final class SubViewModel: ObservableObject {}
If I run the app, tap on StartCooking
, than on StopCooking
and check the memory graph, I still see an instance of SubViewModel
, which means that there is a leak in this code.
If I remove:
NavigationView
OR- The VStack from
ContainerView
OR matchedGeometryEffect
ORtapGesture
The retain cycle is resolved. Unfortunately I need all these. Can you see what the issue might be and how could it be solved?
CodePudding user response:
Looks like a SwiftUI bug. A possible workaround (if sub-view is one or limited set) is to use view model factory to provided instances.
Here is an example for one view:
struct SubView: View {
@StateObject var viewModel = SubViewModel.shared // single instance !!
// .. other code
}
final class SubViewModel: ObservableObject {
static var shared = SubViewModel() // << this !!
}
CodePudding user response:
I could kind of workaround it by making every property optional in the SubViewModel
and running a function when the SubView
s disappear, which makes them nil
. The SubViewModel
still stays in the memory, but will not take up that much space.
Interestingly I even tried to make the viewmodel optional, and make it nil when the view disappears, but it still stayed in the memory.