관리 메뉴

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

[iOS - SwiftUI] @ObservedObject, @StateObject 개념, 차이점, 사용 방법 (MVVM 패턴) 본문

iOS 기본 (SwiftUI)

[iOS - SwiftUI] @ObservedObject, @StateObject 개념, 차이점, 사용 방법 (MVVM 패턴)

jake-kim 2022. 10. 28. 23:26

목차) SwiftUI의 기본 - 목차 링크

 

* @Published, @objecervableObject 개념은 Combine이므로, Combine 관련 이전 포스팅 글 참고

@ObservedObejct 란?

https://developer.apple.com/documentation/swiftui/observedobject

  • observable 객체를 구독하는 property wrapper
    • observable 객체가 변경되면 뷰에 업데이트 시켜주는 기능
  • 내부 코드
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper @frozen public struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {
    @dynamicMemberLookup @frozen public struct Wrapper {
        public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> { get }
    }
    public init(initialValue: ObjectType)
    public init(wrappedValue: ObjectType)
    public var wrappedValue: ObjectType
    public var projectedValue: ObservedObject<ObjectType>.Wrapper { get }
}
  • @ObservedObject 사용 방법 
    • ObservableObject를 준수하는 모델을 만들고, 그 모델에서 값이 변경되면 뷰에 반영하기 위함
    • ObservableObejct를 준수하는 인스턴스를 참조하기 위해서 @ObservedObject로 선언하여 참조

ex) @ObservedObject 사용하여 MVVM 패턴 만들기

  • 모델 정의 (ObservableObject를 준수)
    • ObservableObject는 class 형태만 가능하므로 struct가 아님을 주의
    • @Published는 이전 포스팅 글 Combine 참고
final class MyViewModel: ObservableObject {
  @Published var isOn = false
  
  func toggle() {
    isOn.toggle()
  }
}
  • 위 모델의 인스턴스를 @ObservedObject로 참조
struct ContentView: View {
  @ObservedObject var viewModel1 = MyViewModel()
  
  var body: some View {
    VStack {
      Button(viewModel1.isOn ? "on" : "off") {
        viewModel1.toggle()
      }
    }
  }
}

상태가 변경되는것을 확인

@StateObject

https://developer.apple.com/documentation/swiftui/stateobject

  • SwiftUI는 상태가 변경되면 뷰를 처음부터 다시 만들어서 그리는 동작이 있지만, @StateObject를 사용하면 뷰를 다시 만들지 않고 항상 동일한 뷰가 사용
  • 내부 코드
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
@frozen @propertyWrapper public struct StateObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {
    @inlinable public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
    public var wrappedValue: ObjectType { get }
    public var projectedValue: ObservedObject<ObjectType>.Wrapper { get }
}

ex) @StateObject 사용 방법

  • @ObservedObject와 동일하게 ObservableObject를 만들고, 그 인스턴스를 @StateObject로 만들어서 사용
struct ContentView: View {
  // @ObservedObject var viewModel1 = MyViewModel()
  @StateObject var viewModel2 = MyViewModel()
  
  var body: some View {
    VStack {
      Button(viewModel2.isOn ? "on" : "off") {
        viewModel2.toggle()
      }
    }
  }
}

final class MyViewModel: ObservableObject {
  @Published var isOn = false
  
  func toggle() {
    isOn.toggle()
  }
}

@ObjervedObject와 @StateObject 차이점

  • 둘 다 ObservableObject를 구독하여, 이 값이 변경되면 뷰에 반영해주는 property wrapper 형태
  • 상태 변경이 있을땐 @ObjervedObject는 뷰를 다시 생성해서 그리지만, @StateObject는 뷰를 다시 생성하지 않고 항상 동일한 뷰가 사용 (효율)
  • 기본적으로 @StateObject를 사용하되, 해당 프로퍼티를 subview에게도 주입시켜야 한다면, @ObservedObject로 선언하여 사용할것
    • subview에 @StateObject 프로퍼티를 주입하면, 해당 @StateObject의 수명 주기가 두 곳에서 관리가 되므로 의존성을 줄이기 위해 @ObservedObejct를 사용

@ObservedObject와 @StateObject 차이 코드로 이해하기

ex) 부모 뷰에서 상태값이 업데이트 되어 뷰가 다시 그려질때, ObservableObject를 구독하고 있는 subview의 상태값이 초기화 되는지 유무를 테스트

  • 모델 준비
final class MyViewModel: ObservableObject {
  @Published var isOn = false
  
  func toggle() {
    isOn.toggle()
  }
}
  • ObservedObject로 구독하고 있는 뷰 준비
struct MyView: View {
  @ObservedObject var viewModel = MyViewModel()
  
  var body: some View {
    Button(viewModel.isOn ? "on" : "off") {
      viewModel.toggle()
    }
  }
}
  • 부모 뷰안에 위 뷰를 넣어서, 부모 뷰가 다시 그려지는 상황 준비
struct ContentView: View {
  @State var isOn = false
  
  var body: some View {
    VStack {
      Button(isOn ? "on" : "off") {
        isOn.toggle()
      }
      Divider()
      MyView()
    }
  }
}
  • @ObservedObject로 선언하면 뷰가 다시 생성되어, 상태값들이 초기화 되는 것을 확인
    • 위에있는 on/off 버튼의 상태만 변경되어야 하는데, @ObservedObject로 선언한 subview의 상태값이 초기화 되면서 off로 변경

  • @StateObject로 선언하면 뷰가 다시 생성되지 않아서, 상태값들이 초기화 되지 않는 것을 확인
    • @ObservedObject를 @StateObject로 변경 후 실행
    • 뷰가 다시 그려져도 @StateObject로 상태를 구독하고 있기 때문에 뷰가 다시 생성되지 않아서 상태가 초기화 되지 않음
struct MyView: View {
//  @ObservedObject var viewModel = MyViewModel()
  @StateObject var viewModel = MyViewModel()
  
  var body: some View {
    Button(viewModel.isOn ? "on" : "off") {
      viewModel.toggle()
    }
  }
}

* 전체 코드: https://github.com/JK0369/ExObservedObject-SwiftUI

 

* 참고

https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

https://developer.apple.com/documentation/swiftui/stateobject

 

https://developer.apple.com/documentation/swiftui/observedobject

https://www.avanderlee.com/swiftui/stateobject-observedobject-differences/

Comments