오늘은 State and Data Flow 2탄입니다 :)
Model Data section에 있는 것들을 살펴보겠습니다.
이 글에서 다뤄지는 주요 내용은 다음과 같습니다 :)
- ObservedObject
- EnvironmentObject
- StateObject
이전 글과는 다르게 이번에는 문서를 번역하면서 제가 이해한 바를 녹여보도록 하겠습니다 ㅎㅎ
iOS에서 MVVM 아키텍처를 많이 가져가는데 그와 관련된 내용을 담고 있는 것 같습니다.
뷰와 데이터 모델을 분리하면 테스트 하기가 용이해지고 모듈화를 하기 좋습니다.
SwiftUI를 쓰기 이전에는 데이터의 변경이 일어나면
그 데이터 변경에 의해 화면이 변화되게 된다면 직접 업데이트를 해주어야 했지만 ( 혹은 rxswift... 로 바인딩... )
SwiftUI 가 제공해주는 것을 사용하면 해당 프로퍼티의 변화를 관찰하고 화면을 자동으로 업데이트 해준다고 합니다 !
그럼 어떻게 그게 가능한지 확인해볼까요?
Managing model data in your app
앱에서 데이터 모델 관리하기
Overview
개요
You typically store and process data in your app using a data model that’s separate from your app’s user interface and other logic. The separation promotes modularity, improves testability, and makes it easier to reason about how your app works.
Traditionally, you use a view controller to move data back and forth between the model and the user interface, but SwiftUI handles most of this synchronization for you. To update views when data changes, you make your data model classes observable objects, publish their properties, and declare instances of them using special attributes. To ensure user-driven data changes flow back into the model, you bind user interface controls to model properties. Working together, these features help you to maintain a single source of truth for your data.
일반적으로 앱의 사용자 인터페이스 및 기타 논리와 별도의 데이터 모델을 사용하여 앱에서 데이터를 저장하고 처리합니다. 분리는 모듈성을 촉진하고 테스트 가능성을 개선하며 앱 작동 방식에 대한 추론을 더 쉽게 만듭니다.
전통적으로 뷰 컨트롤러를 사용하여 모델과 사용자 인터페이스 간에 데이터를 앞뒤로 이동하지만 SwiftUI는 이러한 동기화의 대부분을 자동으로 처리합니다. 데이터가 변경될 때 보기를 업데이트하려면 데이터 모델 클래스를 관찰 가능한 개체로 만들고 해당 속성을 게시하고 특수 속성을 사용하여 해당 인스턴스를 선언합니다. 사용자 주도 데이터 변경 사항이 모델로 다시 흐르도록 하려면 사용자 인터페이스 컨트롤을 모델 속성에 바인딩합니다. 이러한 기능을 함께 사용하면 데이터에 대한 단일 정보 소스를 유지 관리할 수 있습니다.
Make model data observable
모델 데이터를 관찰 가능하게 만들기
To make the data changes in your model visible to SwiftUI, adopt the ObservableObject protocol for model classes. For example, you can create a Book class that’s an observable object:
모델의 데이터 변경 사항을 SwiftUI에 표시하려면 모델 클래스에 ObservableObjecct 프로토콜을 채택하십시오. 예를 들어 관찰 가능한 객체인 Book 클래스를 만들 수 있습니다 .
The system automatically infers the ObjectWillChangePublisher associated type for the class and synthesizes the required objectWillChange method that emits the changed values of published properties. To publish a property, add the Published attribute to the property’s declaration:
시스템은 클래스에 대한 ObjectWillChangePublisher 관련 유형을 자동으로 유추하고 게시된 속성의 변경된 값을 내보내는 objectWillChange 메서드를 합성합니다. 해당 프로퍼티를 publish 하려면 Published attribute를 해당 propery를 선언할 때 추가하세요.
class Book: ObservableObject {
@Published var title = "Great Expectations"
}
Avoid the overhead of a published property when you don't need it. Only Publish properties that both can change and that matter to the user interfaces. For example, the Book class might have an identifier propery that never changes after initialization
필요하지 않을 때는 published property의 overhead를 피하세요 ( 쓰지말라는 뜻 ) 변화가 일어날 수 있고 유저의 화면에 표시되는 경우에만 property를 publish하세요. 예를 들어 Book class에는 선언된 후 변경되지 않는 identifier라는 프로퍼티가 있을 수 있습니다.
class Book: ObservableObject {
@Published var title = "Great Expectations"
let identifier = UUID() // A unique identifier that never changes.
}
You can still display the identifier in your user interface, but because it isn’t published, SwiftUI knows that it doesn’t have to watch that particular property for changes.
화면에 해당 identifier를 보여줄 수는 있겠지만 이건 published가 아닙니다.( 바뀌지 않기 때문 ) SwiftUI는 이 property가 변화하는지 지켜볼 필요가 없다는 것을 압니다.
Monitor changes in observable objects
관찰 가능한 개체의 변경 사항 모니터링
To tell SwiftUI to monitor an observable object, add the ObservedObject attribute to the property’s declaration:
SwiftUI에 관찰 가능한 객체를 모니터링하도록 지시하려면 속성 선언에 ObservedObject 속성을 추가하세요
struct BookView: View {
@ObservedObject var book: Book
var body: some View {
Text(book.title)
}
}
You can pass individual properties of an observed object to child views, as shown above. When the data changes, like when you load new data from disk, SwiftUI updates all the affected views. You can also pass an entire observable object to a child view and share model objects across levels of a view hierarchy:
위와 같이 관찰된 개체의 개별 속성을 자식 보기에 전달할 수 있습니다. 디스크에서 새 데이터를 로드할 때와 같이 데이터가 변경되면 SwiftUI는 영향을 받는 모든 뷰를 업데이트합니다. 전체 관찰 가능한 객체를 자식 뷰에 전달하고 뷰 계층 구조의 수준에서 모델 객체를 공유할 수도 있습니다.
struct BookView: View {
@ObservedObject var book: Book
var body: some View {
BookEditView(book: book)
}
}
struct BookEditView: View {
@ObservedObject var book: Book
// ...
}
위에서 만든 ObservedObject를 채택한 데이터 모델을 뷰에서 관찰하게 하려면 뷰 내에서 해당 모델을 선언할 때
@ObservedObject를 붙여서 해주면 됩니다 ! 이 데이터 모델을 하위 뷰에도 전달할 수 있습니다 :)
Instantiate a model object in a view
뷰에서 모델 개체 인스턴스화
SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view. Instead, SwiftUI provides the StateObject attribute for this purpose. You can safely create a Book instance inside a view this way:
SwiftUI는 언제든지 뷰를 생성하거나 다시 생성할 수 있으므로 주어진 입력 세트로 뷰를 초기화하면 항상 동일한 뷰가 생성되는 것이 중요합니다. 결과적으로 뷰 내부에 관찰된 개체를 만드는 것은 안전하지 않습니다. 대신, SwiftUI는 이 목적을 위해 StateObject 속성을 제공합니다. 다음과 같은 방법으로 뷰 내부에 안전하게 Book 인스턴스를 생성할 수 있습니다 .
여기서 나온 StateObject는 ObservedObject를 사용했을 때 이슈가 생기는 부분을 보완해줍니다 !
iOS 14에서 추가되었다고 하네요
이 둘의 자세한 차이는 다음 게시글에서 예제와 함께 찾아오겠습니다 :)
struct LibraryView: View {
@StateObject private var book = Book()
var body: some View {
BookView(book: book)
}
}
A state object behaves like an observed object, except that SwiftUI knows to create and manage a single object instance for a given view instance, regardless of how many times it recreates the view. You can use the object locally, or pass the state object into another view’s observed object property, as shown in the above example.
While SwiftUI doesn’t recreate the state object within a view, it does create a distinct object instance for each view instance. For example, each LibraryView in the following code gets a unique Book instance:
상태 객체는 관찰된 객체처럼 행동합니다. 단, SwiftUI는 뷰를 재생성하는 횟수에 관계없이 주어진 뷰 인스턴스에 대해 단일 객체 인스턴스를 생성하고 관리하는 것을 알고 있습니다. 객체를 로컬로 사용하거나 위의 예와 같이 상태 객체를 다른 뷰의 관찰된 객체 속성에 전달할 수 있습니다.
SwiftUI는 뷰 내에서 상태 객체를 다시 생성하지 않지만 각 뷰 인스턴스에 대해 별개의 객체 인스턴스를 생성합니다. 예를 들어 다음 코드의 각각의 LibraryView는 고유한 Book 인스턴스를 얻습니다.
VStack {
LibraryView()
LibraryView()
}
You can also create state object in your top level App instance, or in one of your app's Scene instatnces. For example, if you define an observable object called Library to hold a collection of books for a book reader app, you could create a single library instance in the app's top level structure:
최상위 App인스턴스 또는 앱 Scene인스턴스 중 하나에서 상태 개체를 만들 수도 있습니다. 예를 들어, Library책 리더 앱에 대한 책 컬렉션을 보유하기 위해 호출되는 관찰 가능한 개체를 정의하는 경우 앱의 최상위 구조에서 단일 라이브러리 인스턴스를 만들 수 있습니다.
@main
struct BookReader: App {
@StateObject private var library = Library()
// ...
}
Share an object throughout your app
앱 전체에서 개체 공유
If you have a data model object that you want to use throughout your app, but don’t want to pass it through many layers of hierarchy, you can use the environmentObject(_:) view modifier to put the object into the environment instead:
앱 전체에서 사용하고 싶지만 여러 계층의 계층을 통과하지 않으려는 데이터 모델 개체가 있는 경우 environmentObject(_:) 보기 수정자를 사용하여 대신 개체를 환경에 배치할 수 있습니다.
@main
struct BookReader: App {
@StateObject private var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
Any descendant view of the view to which you apply the modifier can then access the data model instance by declaring a property with the EnvironmentObject attribute:
수정자를 적용하는 뷰의 모든 하위 뷰는 속성을 사용하여 EnvironmentObject 속성을 선언하여 데이터 모델 인스턴스에 액세스할 수 있습니다.
struct LibraryView: View {
@EnvironmentObject var library: Library
// ...
}
앱 전체적으로 사용하고 싶은 데이터 모델이 있을 수 있겠죠 ?
그럴 때는 EnvironmentObject 속성을 사용해주면 됩니다 :)
If you use an environment object, you might add it to the view at the top of your app’s hierarchy, as shown above. Alternatively, you might add it to the root view of a sub-tree in your view hierarchy. Either way, remember to also add it to the preview provider of any view that uses the object, or that has a descendant that uses the object:
환경 개체를 사용하는 경우 위와 같이 앱 계층 구조의 맨 위에 있는 보기에 추가할 수 있습니다. 또는 보기 계층 구조에 있는 하위 트리의 루트 보기에 추가할 수 있습니다. 어느 쪽이든, 개체를 사용하거나 개체를 사용하는 하위 항목이 있는 보기의 미리 보기 공급자에도 추가해야 합니다.
struct LibraryView_Previews: PreviewProvider {
static var previews: some View {
LibraryView()
.environmentObject(Library())
}
}
Create a two-way connection using bindings
바인딩을 사용하여 양방향 연결 만들기
When you allow the user to change the data in the user interface, use a binding to the corresponding property. This ensures that updates flow back into the data model automatically. You can get a binding to an observed object, state object, or environment object property by prefixing the name of the object with the dollar sign ($). For example, if you let the user edit the title of a book by adding a TextField to the BookEditView, give the text field a binding to the book’s title property:
사용자가 사용자 인터페이스에서 데이터를 변경할 수 있도록 허용하는 경우 해당 속성에 대한 바인딩을 사용합니다. 이렇게 하면 업데이트가 데이터 모델로 자동으로 다시 흐르게 됩니다. 개체 이름 앞에 달러 기호( $)를 붙여 관찰 개체, 상태 개체 또는 환경 개체 속성에 대한 바인딩을 얻을 수 있습니다. 예를 들어 BookEditView title에 TextField를 추가하여 사용자가 책의 제목을 편집할 수 있도록 하는 경우 텍스트 필드에 책의 속성에 대한 바인딩을 지정합니다.
struct BookEditView: View {
@ObservedObject var book: Book
var body: some View {
TextField("Title", text: $book.title)
}
}
The binding connects the view element to the underlying model so that the user makes changes directly to the model data.
바인딩은 사용자가 모델 데이터를 직접 변경할 수 있도록 뷰 요소를 기본 모델에 연결합니다.
기본적으로는 View에서 ObservedObject의 Published 프로퍼티의 값을 관찰하여 뷰를 업데이트 해주었습니다.
사용자의 입력을 받는 TextField와 같은 것은 사용자의 입력이 데이터 모델에 반영되어야겠죠?
이럴땐 양방향 바인딩을 할 수 있도록 제공합니다 :)
마치며
어떻게 보면 간단해보이지만 막상 앱을 만들면서 적용을 해보면 생각보다 어려운 부분도 있다고 생각이 듭니다 :)
다음 게시글에서는 위에서 소개한 속성들을 활용한 예제를 만들어서 가져와보려고 합니다.
언제 ObservedObject를 쓰고 언제 StateObject를 써야할까요 ?Published로 선언된 값이 여러개고 이 값들이 한꺼번에 변경되면 어떻게 될까요 ?
EnvironmentObject는 어떨 때 쓰는게 좋을까요 ?
글을 번역해보면서 생겼던 의문증들을 하나씩 풀어보려고 합니다 :) 다음 글도 기대해주세요 !
'iOS > swiftUI' 카테고리의 다른 글
[swiftUI] State and Data Flow - [1탄] (0) | 2022.07.10 |
---|---|
[swiftUI] GeometryReader - 화면비율에 따라 UI 조절 (0) | 2022.02.28 |
[swiftUI] 스위프트UI 시작하기 - 선언형 UI 가 뭘까? (0) | 2022.01.16 |