norekhov
norekhov

Reputation: 4233

SwiftUI GeometryReader causes memory leak

Consider the following code:

import SwiftUI

class ViewModel: ObservableObject {
}

struct TestView: View {
    @ObservedObject var vm = ViewModel()

    var body: some View {
//        self.sample
        GeometryReader { _ in
            self.sample
        }
    }

    var sample: some View {
        Text("Hello, World!")
    }
}

struct Tabs : View {
    @State var selection: Int = 0

    var body: some View {
        TabView(selection: $selection) {
            TestView().tabItem {
                Text("First Tab")
            }
            .tag(0)

            Text(String(selection))
            .tabItem {
                Text("Second Tab")
            }
            .tag(1)
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

There are two tabs and selection is referenced in body therefore body will be called when selection is changed. TestView is using GeometryReader. When I switch from "First Tab" to "Second Tab" ViewModel is created again and never dereferenced. This is unexpected. If I switch 100 times I will have 100 ViewModels referenced from SwiftUI internals.

Though if i remove GeometryReader it works as expected.

Did someone experience it? Are there any workarounds? I simply want this ViewModel lifetime to be bound to TestView lifetime.

UPDATE:

XCode 11.3.1 iOS 13.3

Upvotes: 1

Views: 1183

Answers (1)

Asperi
Asperi

Reputation: 257493

Ok, let's make the following changes in ViewModel

class ViewModel: ObservableObject {
    init() {
        print(">> inited") // you can put breakpoint here in Debug Preview
    }
}

so now it seen that because View is value type

struct TestView: View {
    @ObservedObject var vm = ViewModel() // << new instance on each creation
    ...

and it is originated from

var body: some View {
    TabView(selection: $selection) {
        TestView().tabItem { // << created on each tab switch
        ...

so, the solution would be to ViewModel creation out of TestView and inject outer instance either via .environmentObject or via constructor arguments.

Btw, it does not depend on GeometryReader. Tested with Xcode 11.2.1 / iOS 13.2

Upvotes: 4

Related Questions