WWDC/Swift

[Swift] WWDC23 - Build an app with SwiftData

유정주 2023. 6. 12. 20:59
반응형

서론

SwiftData 세 번째 영상입니다.

SwiftData는 새로운 데이터 저장, 관리 프레임워크로, CoreData를 대체하는 새로운 프레임워크입니다.

WWDC23 - Meet SwiftData에서 SwiftData가 무엇인지 간략히 소개하고 있습니다.

 

이번 영상은 SwiftUI에서 SwiftData를 사용해 앱을 만드는 방법을 다룹니다.

실습과 함께 진행되기 때문에 직접 시청하시는걸 추천 드립니다.

 

Meet SwiftData에서 SwiftData는 SwiftUI를 염두해두고 설계되었기 때문에 SwiftUI에서 사용이 쉽다고 언급했습니다.

이번 영상에서 그 장점을 느낄 수 있을듯 합니다.

(취업 전까진 UIKit에 집중하고, SwiftUI는 취업하면 공부하자는 계획이 이렇게 악영향을 줄지는 몰랐습니다 ㅎㅎ;)

 

 

참고로 SwiftData 영상은 총 5개로 순서는 아래와 같습니다.

  1. Meet SwiftData
  2. Build an app with SwiftData, Migrate to SwiftData
  3. Model your schema with SwiftData
  4. Dive deeper into SwiftData

 

WWDC23에서는 영상 챕터를 지원합니다.

이번 포스팅도 영상 챕터를 기준으로 작성되었습니다.

 

 

Meet the app

이 영상에서는 SwiftData를 활용해 플래시 카드 앱을 만듭니다.

SwiftData로 날짜와 저자를 저장할건데,

SwiftData는 모든 플랫폼에서 지원하기 때문에 Mac, iPhone, Watch, TV 등 모든 플랫폼 앱에서 사용할 수 있습니다.

 

작업 순서는 아래와 같습니다.

  1. 모델 정의
  2. 데이터 쿼리
  3. UI 바인딩
  4. 문서 기반 앱 만들기

 

SwiftData models

SwiftData 사용 전 코드입니다.

ObservableObject 프로토콜을 준수하고 있고, @Published 프로퍼티 래퍼를 사용하고 있습니다.

 

아래는 @Model 매크로를 이용해 SwiftData 모델로 전환한 코드입니다.

@Model
final class Card {
    var front: String
    var back: String
    var creationDate: Date

    init(front: String, back: String, creationDate: Date = .now) {
        self.front = front
        self.back = back
        self.creationDate = creationDate
    }
}

 

 

@Model은 Observable 프로토콜을 준수하므로 ObservableObject 대신 사용할 수 있게 됩니다.

또한, Observable 객체와 @Published 프로퍼티 래퍼를 제거할 수 있습니다.

 

 

다음은 View 코드입니다.

SwiftData 사용 전에는 ObservedObject를 사용했습니다.

 

@Bindable var card: Card

SwiftData에서는 Bindable을 사용합니다.

 

 

이렇듯 새로 나온 Observable 매크로와 Bindable 프로퍼티 래퍼를 사용하면 훨씬 적은 코드로 앱의 데이터 흐름을 설정할 수 있습니다.

View가 Observable 매크로를 사용하는 경우, 주어진 프로퍼티의 변화가 자동으로 업데이트 됩니다.

Observable에 대해서는 WWDC23 - Discover Observation with SwiftUI 에서 자세히 다룹니다.

 

 

Querying models to display in UI

다음은 ContentView 파일에서 진행됩니다.

모델을 쿼리하고 UI에 표시하는 단계입니다.

 

 

SwiftData의 모델을 UI에 표시하고 싶을 때 @Query를 사용합니다.

@Query는 SwiftData에서 모델을 쿼리하는 새로운 프로퍼티 래퍼로 @State와 비슷하게 동작합니다. 

즉, 모델이 변경될 때마다 업데이트된 View를 트리거합니다.

모든 View는 필요한 만큼 쿼리된 프로퍼티를 가질 수 있고, 정렬, 필터 등 간단한 문법을 제공합니다.

 

 

내부적으로는 View의 ModelContext를 데이터 소스로 사용합니다.

ModelContext는 ModelContainer에서 가져올 수 있습니다. (ModelContext 설명은 바로 챕터에서 나옵니다.)

 

ModelContainer는 새로운 View Modifier로 SwiftUI에서는 .modelContainer로 쉽게 가져올 수 있습니다.

SwiftData를 사용하려면 모든 앱이 하나 이상의 ModelContainer를 설정해야 합니다.

만약 ModelContainer를 설정하지 않는다면 SwiftData를 통해 모델을 저장하거나 쿼리할 수 없습니다.

 

 

단일 ModelContainer가 필요한 경우, WindowGroup을 사용하면 됩니다.

물론 서로 다른 Window에 대해 다른 ModelContainer를 설정할 수 있습니다.

별도의 ModelContainer를 사용한다면 한 곳의 변화가 다른 컨테이너에 영향을 주지 않습니다.

 

 

Creating and updating

ModelContext를 이용해 SwiftData의 변경 사항을 추적할 수 있습니다.

 

ModelContext는 새로운 환경 변수입니다.

ModelContainer처럼 각 View에 단일 ModelContext가 존재하지만, 필요한 만큼 추가할 수 있습니다.

 

let newCard = Card(front: "Sample Front", back: "Sample Back")
modelContext.insert(object: newCard)

객체를 생성하고 ModeContext에 전달하면 저장이 완료됩니다.

SwiftData는 UI 생명 주기나 유저 이벤트를 트리거하여 자동 저장이 되므로 명시적으로 save를 호출하지 않아도 됩니다.

단, 모든 변경사항이 제대로 저장되었는지 확신하고 싶을 때는 명시적으로 save( )를 호출하면 됩니다.

(SwiftData 스토리지를 공유하거나 전송하기 전 등의 시나리오)

 

 

Document-based apps

현재는 모든 윈도우에서 같은 데이터를 표시합니다.

하나의 ModelContext를 공유하고 있기 때문입니다.

다른 창에서 다른 데이터를 표시하고 싶은 경우, 데이터(예제에서는 카드)를 Document로 취급하여 앱에서 사용할 수 있습니다.

 

 

여기서 말하는 Document는 우리에게 익숙한 그 Document가 맞습니다.

사용자는 다양한 유형의 Document를 작성, 열기, 읽기, 편집을 할 수 있고, SwiftData는 이를 지원합니다.

 

@main
struct SwiftDataFlashCardSample: App {
    var body: some Scene {
        #if os(iOS) || os(macOS)
        DocumentGroup(editing: Card.self, contentType: <#UTType#>) {
            <#code#>
        }
        #else
        WindowGroup {
            ContentView()
                .modelContainer(for: Card.self)
        }
        #endif
    }
}

Document를 지원하는 iOS, macOS에서 DocumentGroup을 설정한 코드입니다. (완성 코드는 아래에 있음)

사용자가 데이터를 열 수 있는 앱과 연결할 수 있도록 contentType 파라미터에 적절한 타입을 선언해야 합니다.

(예를 들면, JPEG 이미지 파일이면 사진 앱과 연결할 수 있도록)

 

 

아래는 카드 데이터를 위한 타입 정의 코드입니다.

flashCards라는 새로운 타입을 정의했습니다.

 

이 타입을 Info.plist에 입력합니다.

여기에서 Identifier는 타입 정의 시 exportedAs 파라미터에 전달한 값과 일치해야 합니다.

또한, SwiftData Document는 패키지이기 때문에 Conforms To에 com.apple.package를 입력하여 타입이 이를 준수하는지 확인합니다.

 

 

@main
struct SwiftDataFlashCardSample: App {
    var body: some Scene {
        #if os(iOS) || os(macOS)
        DocumentGroup(editing: Card.self, contentType: .flashCards) {
            ContentView()
        }
        #else
        WindowGroup {
            ContentView()
                .modelContainer(for: Card.self)
        }
        #endif
    }
}

contentType까지 설정한 코드입니다.

위에서 정의한 falshCards 타입으로 설정했습니다.

 

 

이제 실행을 해보면,

Command + S로 저장하거나, Command + N으로 새로운 카드를 만들거나, Command + O로 카드를 열 수도 있습니다.

 

 

감사합니다.


아직은 초보 개발자입니다.

더 효율적인 코드 훈수 환영합니다!

공감 댓글 부탁드립니다.

 

 

반응형