소피it블로그

[SwiftUI] 앱에서 모델 데이터 관리하기 - 공식 문서 번역 본문

개발_iOS/스위프트UI

[SwiftUI] 앱에서 모델 데이터 관리하기 - 공식 문서 번역

sophie_l 2022. 8. 12. 22:12

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

 

Apple Developer Documentation

 

developer.apple.com

1. 개요

 

보통의 경우 앱의 유저 인터페이스와 다른 로직들과 분리된 데이터 모델을 사용하여 데이터를 저장하고 처리한다. 분리를 통해 모듈화가 강화되고 테스트성이 개선되며 앱이 어떻게 작동하는지에 대해 추론하기도 쉬워진다.

전통적으로는 데이터를 모델과 UI 사이에서 주고받게 하기 위해 뷰 컨트롤러를 사용하지만(MVC 패턴), 스위프트UI는 이 대부분의 동기화를 대신 관리해준다. 데이터가 바뀔 때 뷰를 업데이트하기 위해서는 데이터 모델 클래스를 observable object로 만들고 그 프라퍼티를 publish하며 특별한 애트리뷰트를 통해 인스턴스를 선언한다. 유저에 의한 데이터 변경이 모델에 제대로 반영되게 하기 위해서 유저 인터페이스 제어를 모델 프라퍼티에 바인딩한다. 이 기능들은 데이터가 단 하나의 source of truth를 가질 수 있도록 돕는다.

 

2. 모델 데이터를 observable하게 만들기

 

모델에서의 데이터 변경을 스위프트UI에 나타내고 싶을 경우 모델 클래스에 ObservableObject 프로토콜을 채택하라. 예를 들어서 observable 객체인 Book 클래스를 생성할 수 있다.

class Book: ObservableObject {
}

시스템은 자동으로 해당 클래스에 ObjectWillChangePublisher과 관련된 타입을 추론할 것이고 퍼블리시된 프라퍼티의 값의 변화를 발행하는 objectWillChange 메서드를 통합할 것이다. 프라퍼티를 퍼블리시하기 위해 프라퍼티의 선언에 Published 애트리뷰트를 추가하라.

class Book: ObservableObject {
    @Published var title = "Great Expectations"
}

필요하지 않은 경우에는 프라퍼티를 퍼블리시하지 마라. 유저 인터페이스를 바꿀 수 있고 유저 인터페이스에 있어 중요한 프라퍼티만 발행하라. 예를 들어 Book 클래스는 초기화 이후 절대 바뀌지 않는 identifier 프라퍼티를 가질 수 있다.

class Book: ObservableObject {
    @Published var title = "Great Expectations"

    let identifier = UUID() // A unique identifier that never changes.
}

유저 인터페이스에 여전히 identifier를 보여줄 수 있지만 이는 퍼블리시되지 않았기 때문에 스위프트UI는 이 프라퍼티가 변화하는지 관찰할 필요가 없다.

 

3. observable object의 변화를 감지하기

 

스위프트UI가 observable object를 주시하게 하려면 프라퍼티의 선언에 ObservedObject 애트리뷰트를 추가하라.

struct BookView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        Text(book.title)
    }
}

이 observed object의 개별 프라퍼티들은 위와 같이 자식 뷰들에 전달할 수 있다. 디스크에서 새 데이터를 로딩해오는 등에 의해 데이터가 변경되면 스위프트UI는 영향을 받은 뷰들 전부를 업데이트한다. 또한 observable object 전체를 자식 뷰에 전달하고 뷰 위계 내부의 서로 다른 레벨 간에 모델 객체를 공유할 수도 있다.

struct BookView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        BookEditView(book: book)
    }
}

struct BookEditView: View {
    @ObservedObject var book: Book

    // ...
}

4. 뷰 안에서 모델 객체를 인스턴스화하기

 

스위프트UI는 뷰를 언제나 생성하거나 재생성할 수 있기 때문에 특정한 입력의 집합이 늘 같은 뷰로 이어지도록 뷰를 초기화하는 것이 중요하다. 그 결과 뷰 안에 observed object를 생성하는 것은 안전하지 않다. 대신 스위프트UI는 이 목적을 위해 StateObject 애트리뷰트를 제공한다. 이 방법을 통해 Book 인스턴스를 뷰 안에 안전하게 생성할 수 있다.

struct LibraryView: View {
    @StateObject private var book = Book()
    
    var body: some View {
        BookView(book: book)
    }
}

스위프트UI가 뷰를 몇 번 재생성하든지간에 특정 뷰 인스턴스에 대해 단 하나의 객체 인스턴스만을 만들고 관리한다는 것을 알고있다는 점만 제외하면, state 객체는 observed 객체처럼 행동한다. 그 객체는 local에서 사용할 수도 있고 위의 예에서처럼 다른 뷰의 observed 객체 프라퍼티 안으로 state 객체를 전달할 수도 있다.

스위프트UI는 뷰 내부에서 state 객체를 새로 생성하지 않는 한편, 각각의 뷰 인스턴스에 대해 별개의 객체 인스턴스는 창조한다. 예를 들어 다음 코드에서의 각 LibraryView는 각자 고유한 Book 인스턴스를 갖는다.

VStack {
    LibraryView()
    LibraryView()
}

또한 state 객체는 앱의 최상위 레벨의 인스턴스 안에 생성해주거나 앱의 Scene 인스턴스들 중 하나에 생성해줄 수 있다. 예를 들어 북 리더 앱 용으로 책의 콜렉션을 저장하기 위해 Library라고 하는 observable object를 정의하고 싶다면, 앱의 최상위 레벨 구조 내부에 library 인스턴스 하나를 생성해줄 수 있다.

@main
struct BookReader: App {
    @StateObject private var library = Library()

    // ...
}

5. 앱 내부에서 객체를 공유하기

 

앱에서 계속하여 사용하고 싶은 데이터 모델이 있지만 위계질서 내의 수많은 계층을 건너 이를 전달하고 싶지 않을 경우 객체를 환경에 넣어주는 environmentObject(_:) 뷰 모디파이어를 사용해줄 수 있다.

@main
struct BookReader: App {
    @StateObject private var library = Library()
    
    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environmentObject(library)
        }
    }
}

해당 모디파이어를 적용해준 뷰의 자식 뷰들은 전부 EnvironmentObject 애트리뷰트를 통해 프라퍼티를 선언해줌으로써 데이터 모델 인스턴스에 접근할 수 있다.

struct LibraryView: View {
    @EnvironmentObject var library: Library
    
    // ...
}

environment 객체를 사용할 경우 위의 예시에서처럼 앱의 위계상의 최상층에 있는 뷰에 추가를 할 수 있다. 혹은 뷰 위계질처 내부에서 하위 트리들을 갖는 뿌리 뷰에 추가해줄 수도 있다. 어떤 방식을 채택하든 간에 해당 객체를 사용하는 뷰들의 프리뷰 프로바이더나 자손 뷰에도 이를 추가해주는 것을 잊지 마라.

struct LibraryView_Previews: PreviewProvider {
    static var previews: some View {
        LibraryView()
            .environmentObject(Library())
    }
}

6. 바인딩을 사용하여 쌍방향 연결을 생성하기

 

유저로 하여금 유저 인터페이스에서 데이터를 변경할 수 있도록 할 경우, 해당되는 프라퍼티에 바인딩을 사용하라. 이를 통해 업데이트된 사항들이 데이터 모델에 자동적으로 전달될 수 있다. observed object, state object, environment object 프라퍼티에 해당 객체 앞에 달러 기호($)를 붙여줌으로써 바인딩을 설정해줄 수 있다. 예를 들어, BookEditingView에서 TextField를 추가함으로써 유저가 책의 제목을 편집할 수 있게 한다면 해당 텍스트 필드에 책의 title 프라퍼티에 대한 바인딩을 설정하라.

struct BookEditView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        TextField("Title", text: $book.title)
    }
}

바인딩은 뷰 요소와 모델을 연결지어줌으로써 유저가 모델 데이터에 직접적으로 변경 사항을 반영하도록 한다.

'개발_iOS > 스위프트UI' 카테고리의 다른 글

[SwiftUI] ObservedObject 정리  (0) 2022.08.12
[SwiftUI] Binding 정리  (0) 2022.08.11
[SwiftUI] State 정리  (1) 2022.08.11
[SwiftUI] 스위프트 UI 컴포넌트 정리  (0) 2022.05.01