[SwiftUI] Singleton? No! Use EnvironmentObject
주절주절 서론
SwiftUI 를 처음 다뤘을 때, 여러 뷰에서 공통된 데이터 인스턴스에 접근하게 하기 위해 이전에 UIKit에서 사용했던 Singleton 패턴을 사용했었습니다. 하지만 속성들을 사용해서 뷰모델을 관리하는 SwiftUI 의 코딩방식과는 거리감이 느껴진다는 것을 깨달았습니다. 값이 변할 때마다 뷰에 변화를 알릴 수 있도록 데이터 클래스가 ObservableObject
를 준수하도록 했지만 이런 클래스에 싱글톤 인스턴스를 만드는건 분명 이상한 모양이었습니다. 하위 뷰마다 ObservedObject
를 사용해 하나하나 전달할 수 있겠지만 뷰계층 사이에 해당 뷰모델이 필요없는 뷰도 분명히 있을 수 있기 때문에 불필요한 전달이 발생할 수 있습니다. 그렇다면 과연 SwiftUI 에서는 Singleton 말고 어떻게 뷰에서 접근하는 데이터를 하나로 관리할 수 있을까요?
애플 문서 내용
상위 뷰에 의해 제공되는 observable object(뷰모델) 를 위한 속성입니다.
environment 객체는 observable 객체(뷰모델)가 변할때마다 현재 뷰를 무효화합니다. environment 객체를 선언할 때 상위 뷰에서environmentObject(_:)
modifier을 사용하여 해당 객체를 세팅하도록 합니다.
EnvironmentObject 사용하기
상위 뷰에서 생성한 observable object를 .environmentObject(_:)
modifier 를 사용하여 하위뷰에서 접근할 수 있도록 합니다.
@main
struct BookReader: App {
@StateObject var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
하위 뷰에서는 @EnvironmentObject
을 사용하여 해당 뷰모델에 접근합니다.
struct LibraryView: View {
@EnvironmentObject var library: Library
// ...
}
주의: Preview에 동일하게
.environmentObject(library)
을 적용하여 뷰모델에 제대로 접근할 수 있도록 합니다.
ObservedObject 와는 뭐가 다른가?
여러 뷰에서 공통되는 뷰모델을 사용하는 방법은 크게 두 가지가 있습니다.
- 상위 뷰에서 하위 뷰로 해당 모델을 넘겨주는 방법
- 모든 뷰에서 접근 할 수 있는 모델을 생성하는 방법
전자의 경우 ObservedObject 를 사용하여 observable object(뷰모델) 을 상위뷰로 부터 넘겨받으면 됩니다.
후자의 경우 EnvironmentObject 를 사용하여 공통된 뷰모델을 앱 전체 어디에서나 즉각 접근할 수 있습니다. 즉, Singleton 패턴에서 사용하는 shared static 프로퍼티처럼 생각할 수 있습니다.
앱에서 많은 양의 데이터를 전달해야할 필요가 있을 때는 ObservedObject 로 직접 넘겨주는 것보다 EnvironmentObject를 사용하는 방법이 가장 편리 합니다. 또한 모든 뷰들이 동일한 model 를 바라보고 있기 때문에 특정 뷰에 의해 데이터가 변경되어도 뷰들간의 데이터 싱크를 걱정하지 않아도 됩니다.