10

I want my @StateObject to be deinitialized as soon as possible after I navigate back, but it seems that the object is held in memory. "Deint ViewModel" is not being printed on back navigation, its first printed after I navigate again to the View I was coming from. Is there a way to release the @StateObject from memory on back navigation?

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: TestView(), label: { Text("Show Test View") })
        }
    }
}

struct TestView: View {
    
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        Text("Test View")
    }
}

final class ViewModel: ObservableObject {
    deinit {
        print("Deint ViewModel")
    }
}
KevinP
  • 2,562
  • 1
  • 14
  • 27

3 Answers3

3

I don't have a brilliant answer on all situations that prevent the deinitialization the @StateObject, but I found that leaving background async tasks running prevents the deinitialization.

In my case, I had several cancellables registered to listen to PassthroughSubject and/or CurrentValueSubject (that I used to handle external changes on my model and exposing the result to the view), but I never cancelled them. As soon as I did it in the view using .onDisappear, it worked.

So my views all "subscribe" to the view model (I have a viewModel.subscribe() method) using .onAppear and then "unsubscribe" to the view model (I have a viewModel.subscribe() method) using .onDisappear. Doing so, the @StateObject is deinitialized when the view is dismissed.

GregP
  • 115
  • 1
  • 6
1

Adding to GregP's answer: If you have removed all of your cancellables on onDisappear and deinit is still not being called, you may use the Debug Memory Graph Debug Memory Graph

Navigate to the object, see its tree and see what else is referencing it. For example I had it looking like this: Extra reference

Because there was another object referencing this object it didn't get removed from the memory (ARC). So all I had to do was remove it from being the delegate along with cancelling the cancellables and deinit got called

-1

I think you should use @ObservedObject private var viewModel: ViewModel instead, then inject new ViewModel instance from outside of TestView

Long Vu
  • 160
  • 1
  • 11
  • Can you explain to me how this would help to deinit ViewModel when navigating back? The real problem is that on navigating back, TestView is still in memory, thats why ViewModel is not beeing deinitialized when expected. I just think thats a "feature" of the NavigationView. – KevinP Dec 19 '20 at 12:48
  • If you use `StateObject`, your view model will not being destroy. use observedObject instead. you can check https://www.donnywals.com/whats-the-difference-between-stateobject-and-observedobject/ to more clearly – Long Vu Dec 19 '20 at 14:18
  • A StateObject's deinit is called. As of now I experience a problem where it is not called is I still have some async/await calls running on it, but, if I don't the de-init does get called on the ViewModel. – zumzum Sep 20 '21 at 23:37