2

I faced the problem when NavTestChildView called more one times. I don't understand what going wrong. I tested on a real device with iOS 16.0.3 and emulator Xcode 14.0.1

I replaced original code to give more info about the architecture why I create NavTestService into navigationDestination.

enum NavTestRoute: Hashable {
    case child(Int)
}

class NavTestService: ObservableObject {
    let num: Int
    
    init(num: Int) {
        self.num = num
        print("[init][NavTestService]")
    }

    deinit {
        print("[deinit][NavTestService]")
    }
}

struct NavTestChildView: View {
    @EnvironmentObject var service: NavTestService

    init() {
        print("[init][NavTestChildView]")
    }

    var body: some View {
        Text("NavTestChildView \(service.num)")
    }
}

struct NavTestMainView2: View {
    var body: some View {
        VStack {
            ForEach(1..<10, id: \.self) { num in
                NavigationLink(value: NavTestRoute.child(num)) {
                    Text("Open child \(num)")
                }
            }
        }
    }
}

struct NavTestMainView: View {
    var body: some View {
        NavigationStack {
            NavTestMainView2()
                .navigationDestination(for: NavTestRoute.self) { route in
                    switch route {
                    case let .child(num):
                        NavTestChildView().environmentObject(NavTestService(num: num))
                    }
                }
        }
    }
}

logs:

[init][NavTestChildView]
[init][NavTestService]
[deinit][NavTestService]
[init][NavTestChildView]
[init][NavTestService]

2 Answers 2

0

Looks like there is a period when instance of NavTestService is not held by anyone and it leaves the heap. In practice this would hardly ever happen because .environmentObject vars are usually held somewhere up the hierarchy. If you change NavTestMainView accordingly:

struct NavTestMainView: View {
    let navTestService = NavTestService()
    var body: some View {
        NavigationStack {
            NavigationLink(value: NavTestRoute.child) {
                Text("Open child")
            }
            .navigationDestination(for: NavTestRoute.self) { route in
                switch route {
                case .child:
                    NavTestChildView().environmentObject(navTestService)
                }
            }
        }
    }
}

... you get no deinits and no extra init as well. The console will output:

[init()][NavTestService]
[init()][NavTestChildView]
[init()][NavTestChildView]

Also note that if you comment out let navTestService = NavTestService() and wrap NavTestChildView().environmentObject(NavTestService()) in LazyView you'll get the following output:

[init()][NavTestChildView]
[init()][NavTestService]

Where LazyView is:

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Is this related to my question here? stackoverflow.com/questions/73978107/…
I've managed to reproduce your issue, @Andre. If I find and answer, I'll give it.
In my case I can't create NavTestService before users click on a link cause I don't know all parameters
The service needs to either be a singleton or a @stateobject
You are right @malhal. Generally we use .environmentObject() to share the state between views, so navTestService would likely be @StateObject IRL. However making it @StateObject here makes no difference.
|
0

It's not "firing" it's just initing the View struct multiple times which is perfectly normal and practically zero overhead because View structs are value types. It tends to happen because UIKit's event driven design doesn't align well with SwiftUI's state driven design.

You can simplify your code by replacing the router enum / case statement with multiple navigationDestination for each model type.

6 Comments

In this case SwiftUI will make instances of NavTestService when NavTestMainView2 is called. NavigationLink(value: NavTestService(num: 1)) { Text("Open child \(num)") }
We aren't supposed to init objects in View structs
Why? Where should we init objects?
Because the hierarchy of view structs is created, used and destroyed on every state change. If you init an object on the heap during this time then it’s considered a memory leak and performance hit. That’s why you have to use the property wrapper @stateobject when you need a reference type for state however in your case I don’t think you need a reference type.
You wrote about using a model type instead of enum I thought about NavTestService. Could you give an example to clear our conversation?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.