I previously asked a question about how to push a view with data received from an asynchronous callback. The method I ended up with has turned out to cause a Memory Leak.
I'm trying to structure my app with MVVM for SwiftUI, so a ViewModel should publish another ViewModel, that a View then knows how to present on screen. Once the presented view is dismissed from screen, I expect the corresponding ViewModel to be deinitialised. However, that's never the case with the proposed solution.
After UserView
is dismissed, I end up having an instance of UserViewModel
leaked in memory. UserViewModel
never prints "Deinit UserViewModel", at least not until next time a view is pushed on pushUser
.
struct ParentView: View {
@ObservedObject var vm: ParentViewModel
var presentationBinding: Binding<Bool> {
.init(get: { vm.pushUser != nil },
set: { isPresented in
if !isPresented {
vm.pushUser = nil
}
}
)
}
var body: some View {
VStack {
Button("Get user") {
vm.getUser()
}
Button("Read user") {
print(vm.pushUser ?? "No userVm")
}
if let userVm = vm.pushUser {
NavigationLink(
destination: UserView(vm: userVm),
isActive: presentationBinding,
label: EmptyView.init
)
}
}
}
}
class ParentViewModel: ObservableObject {
@Published var pushUser: UserViewModel? = nil
var cancellable: AnyCancellable?
private func fetchUser() -> AnyPublisher<User, Never> {
Just(User.init(id: "1", name: "wiingaard"))
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
func getUser() {
cancellable = api.getUser().sink { [weak self] user in
self?.pushUser = UserViewModel(user: user)
}
}
}
struct User: Identifiable {
let id: String
let name: String
}
class UserViewModel: ObservableObject, Identifiable {
deinit { print("Deinit UserViewModel") }
@Published var user: User
init(user: User) { self.user = user }
}
struct UserView: View {
@ObservedObject var vm: UserViewModel
var body: some View {
Text(vm.user.name)
}
}
After dismissing the UserView
and I inspect the Debug Memory Graph, I see an instance of UserViewModel
still allocated.
The top reference (view.content.vm
) has kind: (AnyViewStorage in $7fff57ab1a78)<ModifiedContent<UserView, (RelationshipModifier in $7fff57ad2760)<String>>>
and hierarchy: SwiftUI.(AnyViewStorage in $7fff57ab1a78)<SwiftUI.ModifiedContent<MyApp.UserView, SwiftUI.(RelationshipModifier in $7fff57ad2760)<Swift.String>>> AnyViewStorageBase _TtCs12_SwiftObject
What's causing this memory leak, and how can I remove it?