관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - SwiftUI] 뷰가 업데이트 될 때 init호출 타이밍 개념 (@State, @StateObject, @ObservedObject, @EnvironmentObject) 본문

iOS 기본 (SwiftUI)

[iOS - SwiftUI] 뷰가 업데이트 될 때 init호출 타이밍 개념 (@State, @StateObject, @ObservedObject, @EnvironmentObject)

jake-kim 2024. 10. 2. 01:12

뷰 상태 관리

  • 상태가 변경되면 뷰가 업데이트 되는데 상태를 관리하는 방법은 크게 3가지
    • ObservableObject 모델을 아래 3가지로 참고
    • @StateObject (혹은@State), @ObservedObject, @EnvironmentObject)
class Person: ObservableObject {
    @Published var name: String
    
    init(name: String) {
        self.name = name
        print("init > Person Model")
    }
}

struct Subview: View {
    @StateObject var personByState = Person(name: "jake")
    @ObservedObject var personByObservedObject: Person = Person(name: "jake")
    @EnvironmentObject var personByEnvironmentObject: Person
}

 

 

뷰가 업데이트 될 때 init호출 타이밍

  • 궁금한 점)
    • 뷰가 재사용 될 때 Preson의 init은 매번 호출될까? 
    • 뷰가 재사용 될 때 해당 뷰의 init은 매번 호출될까?
  • 결과)
    • 뷰가 재사용 될 때 @State, @StateObject를 사용하면 init은 매번 호출 안됨
    • 뷰가 재사용 될 때 모델과 Subview의 init은 @ObservedObject, @environmentObject를 사용하면 매번 호출됨
      • 예외) ObservedObject를 주입받으면 init이 매번 호출되지만, 자기 자신의 뷰에서 초기화해서 사용하면 init은 매번 호출 안됨
      • (또 상태값이 변경되어도 모델을 주입해준 SuperView의 init은 매번 호출되지 않음)

ex)

  • 뷰가 재사용 될 때 @State, @StateObject를 사용하면 init은 매번 호출 안됨
    • Task.sleep을 통해 1초마다 personByState 상태값을 변경해주고 init안에 넣은 print가 찍히는지 확인
struct SuperView: View {
    init() {
        print("init> SuperView")
    }
    
    var body: some View {
        VStack {
            Subview()
        }
    }
}

struct Subview: View {
    @StateObject var personByState = Person(name: "jake")
    @State var counter = 0
    
    init() {
        print("init > Subview")
    }
    
    var body: some View {
        VStack {
            Text(personByState.name + personByObservedObject.name)
            
            Text("\(counter)")
                .onAppear {
                    Task {
                        for _ in 1...100 {
                            personByState.name = "\(counter)"
                            counter += 1
                            try? await Task.sleep(nanoseconds: 1_000_000_000)
                        }
                    }
                }
                .onAppear()
        }
    }
}

뷰가 재사용 될 때 @State, @StateObject를 사용하면 init은 매번 호출 안됨

 

  • 뷰가 재사용 될 때 Preson와 Subview의 init은 @ObservedObject, @environmentObject를 사용하면 매번 호출됨

(ObservedObject 예시)

struct SuperView: View {
    @StateObject var person = Person(name: "jake")
    
    init() {
        print("init> SuperView")
    }
    
    var body: some View {
        VStack {
            Subview(person: person)
        }
    }
}

struct Subview: View {
    @ObservedObject var personByObservedObject: Person
    @State var counter = 0
    
    init(person: Person) {
        _personByObservedObject = .init(wrappedValue: person)
        print("init > Subview")
    }
    
    var body: some View {
        VStack {
            Text(personByState.name + personByObservedObject.name)
            
            Text("\(counter)")
                .onAppear {
                    Task {
                        for _ in 1...100 {
                            personByObservedObject.name = "\(counter)"
                            
                            counter += 1
                            try? await Task.sleep(nanoseconds: 1_000_000_000)
                        }
                    }
                }
                .onAppear()
        }
    }
}

뷰가 재사용 될 때 Preson와 Subview의 init은 @ObservedObject, @environmentObject를 사용하면 매번 호출됨

  • 예외) ObservedObject를 주입받으면 init이 매번 호출되지만, 자기 자신의 뷰에서 초기화해서 사용하면 init은 매번 호출 안됨
struct SuperView: View {    
    init() {
        print("init> SuperView")
    }
    
    var body: some View {
        VStack {
            Subview()
        }
    }
}

struct Subview: View {
    @ObservedObject var personByObservedObject: Person = .init(wrappedValue: person)
    @State var counter = 0
    
    init(person: Person) {
        print("init > Subview")
    }
    
    var body: some View {
        VStack {
            Text(personByState.name + personByObservedObject.name)
            
            Text("\(counter)")
                .onAppear {
                    Task {
                        for _ in 1...100 {
                            personByObservedObject.name = "\(counter)"
                            
                            counter += 1
                            try? await Task.sleep(nanoseconds: 1_000_000_000)
                        }
                    }
                }
                .onAppear()
        }
    }
}

예외) ObservedObject를 주입받으면 init이 매번 호출되지만, 자기 자신의 뷰에서 초기화해서 사용하면 init은 매번 호출 안됨

* 전체 코드: https://github.com/JK0369/ExSwiftUIInit

Comments