스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
SwiftData로 스키마 모델링하기
SwiftData로 스키마 매크로와 마이그레이션 계획을 사용하여 앱에 더 복잡한 기능을 구현하는 방법을 알아보세요. @Attribute 및 @Relationship 옵션으로 지속성을 세밀하게 조정하는 방법을 알려 드립니다. @Transient로 데이터 모델에서 프로퍼티를 제외하는 방법과 스키마의 한 버전에서 다음 버전으로 원활히 마이그레이션하는 방법도 알아보세요. 이 세션을 최대한 활용하려면 WWDC23의 'SwiftData 알아보기'와 'SwiftData로 앱 빌드하기' 세션을 먼저 시청하시는 걸 권장합니다.
챕터
- 0:00 - Intro
- 1:41 - Utilizing schema macros
- 5:30 - Evolving schemas
- 8:56 - Wrap-up
리소스
관련 비디오
WWDC23
-
다운로드
♪ ♪
저는 리시 버마입니다 이 세션에서는 모델을 코딩해 SwiftData 스키마를 빌드하는 법을 소개합니다 가장 먼저 스키마 매크로를 최대한 활용하는 법을 알려 드리겠습니다 그리고 앱이 변경될 때 스키마 마이그레이션으로 스키마를 개선하는 법을 알아보죠 시작하기 전에 'SwiftData 알아보기'와 'SwiftData로 앱 빌드하기' 세션을 봐 주세요 두 세션에서 소개된 개념들을 오늘 더 자세히 다룰 거니까요 SwiftData는 데이터 모델링 및 관리를 위한 강력한 프레임워크로 최신 Swift 앱을 향상합니다 SwiftUI처럼 외부 파일 형식 없이 오로지 코드에만 집중하며 Swift의 새로운 매크로 시스템으로 원활한 API 경험을 제공하죠 제가 개발 중인 SampleTrips 앱에서는 사용자가 여행을 계획할 수 있습니다 각 여행은 이름과 목적지 시작 및 종료 날짜와 함께 생성됩니다 또한 버킷 리스트 항목과 숙소에 대한 관계도 포함할 수 있죠 SwiftData 추가는 간단합니다 import를 추가하고 @Model로 Trip을 데커레이트하면 되죠 그게 다예요 @Model 매크로는 Trip 클래스를 PersistentModel에 일치시키고 기술적 스키마를 생성합니다 이제 모델을 정의하는 코드가 앱 스키마의 진실 공급원이 되었습니다 여행 모델의 기본 동작도 괜찮지만 약간 조정해 보겠습니다 SwiftData 스키마 매크로를 통해 지속적인 경험의 행동이 앱에서 완벽히 작동하도록 맞춤화할 수 있습니다 기존 스키마로 앱을 게시할 때는 각 여행에 고유한 이름을 보장하지 않았습니다 그 결과 이름이 같은 여행끼리 충돌했죠 이를 해결해야 합니다 unique 옵션을 쓰는 @Attribute 스키마 매크로로 해결할 수 있습니다 SwiftData가 생성하는 여행 모델의 스키마가 영구 백 엔드에 저장되는 모든 여행에 고유한 이름을 보장하죠 해당 이름의 여행이 이미 존재하는 경우 영구 백 엔드에서 최근 값으로 업데이트합니다 이를 업서트라고 합니다 업서트는 인서트로 시작합니다 기존 데이터와 충돌한 인서트가 업데이트로 변해 기존 데이터의 프로퍼티를 업데이트하는 것이죠 다른 프로퍼티에도 고유한 제약을 적용할 수 있습니다 숫자나 문자열, UUID와 같은 기본값 유형이라면 가능하죠 아니면 to-one 관계를 데커레이트할 수도 있고요 스키마를 좀 더 수정해 볼게요 이전에 지정한 start_date와 end_date에서 성가신 밑줄을 없애고 싶습니다 변수 이름을 바꾸면 생성된 스키마의 새 프로퍼티로 간주될 겁니다 저는 SwiftData가 새 프로퍼티를 생성하는 건 원하지 않고 기존 데이터를 그대로 유지하고 싶습니다 그러려면 기존 이름을 프로퍼티 이름으로 매핑하면 되죠 @Attribute를 사용하고 originalName: 매개변수를 지정해서요 기존 이름을 매핑하면 데이터 손실을 예방할 수 있습니다 또한 스키마 업데이트가 SampleTrips 앱의 다음 출시에 간단한 마이그레이션을 보장하게 되죠 @Attribute 매크로는 다양한 작업을 수행합니다 대용량 데이터를 외부에 저장하고 변형 가능성을 지원하죠
제 여행이 개선되고 있군요 이제 관계를 살펴보겠습니다 새 버킷 리스트 항목이나 숙소를 여행에 추가하면 SwiftData가 암시적으로 모델 간의 역관계를 발견하고 대신 설정해 줍니다 암시적 역관계에는 주석이 전혀 필요 없습니다 바로 작동하죠 암시적 역관계는 기본 삭제 규칙을 사용하여 여행이 삭제될 때 버킷 리스트 항목과 숙소 프로퍼티를 null로 만듭니다 하지만 저는 버킷 리스트 항목과 숙소가 여행과 함께 삭제됐으면 합니다 @Relationship 매크로와 cascade 삭제 규칙으로 이를 쉽게 수행할 수 있죠 이제 여행을 지우면 해당 관계들도 함께 삭제됩니다 @Relationship 매크로의 기능은 다양합니다 originalName 수정자를 포함하고 to-many 관계에서 최소 및 최대 개수를 지정할 수도 있죠 SampleTrips 앱이 잘 다듬어지고 있지만 아직 업데이트가 남았습니다 제가 여행을 몇 번 조회하는지 추적하는 방법을 추가하겠습니다 그러면 제가 얼마나 휴가를 기대하는지 알 수 있죠 무척 기대 중이거든요 하지만 SwiftData가 조회 수를 계속 세기를 바라지는 않으므로 @Transient 매크로를 사용하겠습니다 @Transient로 프로퍼티를 데커레이트하면 해당 프로퍼티는 지속되지 않습니다 간단하죠? @Transient 매크로는 불필요한 데이터를 지속하지 않게 해 줍니다 transient 프로퍼티에 기본값을 반드시 제공하세요 그래야 SwiftData에서 가져올 때 논리적인 값이 할당됩니다 스키마 매크로 활용에 대한 자세한 정보는 SwiftData 문서를 참조하세요 SampleTrips 앱 스키마는 많은 변화를 거쳐 지속성 경험을 조정했습니다 출시할 때마다 앱이 업데이트를 처리할 수 있게 해야 하죠 프로퍼티 추가 및 제거 등의 스키마 변경이 있을 때는 데이터 마이그레이션이 발생합니다 마이그레이션은 복잡할 수 있지만 SwiftData를 쓰면 간단히 해결됩니다 VersionedSchema와 SchemaMigrationPlan이 도와주죠 SwiftData 모델이 변경된 앱의 새 버전을 출시할 때마다 VersionedSchema를 정의하세요 이는 이전에 출시된 스키마를 캡슐화합니다 각각의 스키마 버전이 VersionedSchema로 정의되어야 버전 사이에 어떤 변화가 있었는지 SwiftData가 알 수 있죠 그런 다음 VersionedSchema의 total ordering을 사용해 SchemaMigrationPlan을 생성하세요 그러면 SwiftData가 필요한 마이그레이션을 순서대로 수행합니다 마이그레이션 계획에 스키마를 정렬했으면 마이그레이션 스테이지를 정의할 수 있는데요 두 가지 유형의 마이그레이션 스테이지가 제공되죠 하나는 경량 마이그레이션입니다 경량 마이그레이션은 다음에 앱을 출시하면서 기존 데이터를 마이그레이션할 때 추가 코드가 필요하지 않습니다 날짜 프로퍼티에 originalName을 추가하거나 관계에서 삭제 규칙을 지정하는 등의 변경은 경량 마이그레이션에 적합합니다 하지만 여행 이름을 고유하게 짓는 건 경량 마이그레이션에 적합하지 않죠 이때는 사용자 지정 마이그레이션 스테이지가 필요합니다 그래야 고유한 이름을 짓기 전에 중복된 여행을 제거할 수 있죠 우선 첫 번째 출시의 기존 스키마를 가져와서 VersionedSchema에 캡슐화합니다 이를 SampleTripsSchemaV1로 지정하겠습니다 각 VersionedSchema는 정의된 모델 클래스를 나열합니다 두 번째 버전 스키마에는 여행 이름에 고유성 제약을 추가했는데요 VersionedSchema를 하나 더 생성해서 Trip 모델 클래스에 적용한 변경 사항을 캡슐화하겠습니다 세 번째 버전 스키마에도 똑같이 해서 시작 및 종료 날짜에 적용된 이름 변경을 캡처합니다 이제 VersionedSchema가 모두 준비됐으니 SchemaMigrationPlan을 구성해서 출시 간 마이그레이션을 처리하는 법을 설명할게요 간단합니다 앱 스키마의 total ordering만 제공하면 되죠 그런 다음 각 마이그레이션이 경량인지 사용자 정의인지 주석을 달아야 합니다 V1에서 V2로의 마이그레이션은 사용자 지정 스테이지가 필요해서 데이터가 마이그레이션되기 전에 작업을 수행해야 하죠 willMigrate 클로저에서 마이그레이션이 진행되기 전에 중복되는 여행을 삭제할 수 있습니다 V1에서 V2로의 마이그레이션이 발생하는 시점을 SwiftData가 감지해 이 클로저를 실행하죠 originalName의 마이그레이션은 경량이므로 해당 스테이지도 추가해야 합니다 마이그레이션 계획의 세부 사항까지 모두 정의했으니 마이그레이션을 실행할 차례입니다 현재 스키마와 마이그레이션 계획이 포함된 ModelContainer를 설정하면 끝입니다 어떤 버전에서든 최신 릴리즈로 업그레이드할 수 있고 데이터도 확실히 보존될 것입니다 어서 SampleTrips 앱으로 휴가를 계획하고 싶네요 스키마 매크로를 활용해 스키마의 추가 메타데이터를 전달하세요 앱이 개선될 때마다 VersionedSchema로 개선점을 캡처해 이전 버전에서도 앱을 마이그레이션할 수 있죠 다른 세션도 확인해 보세요 여러분이 SwiftData로 만들 멋진 작품들이 기대됩니다 감사합니다
-
-
0:56 - Original Trip model
import SwiftUI import SwiftData @Model final class Trip { var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
1:50 - Adding a unique attribute
@Model final class Trip { @Attribute(.unique) var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
2:48 - Specifying original property names
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
4:00 - Cascading delete rule
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date @Relationship(.cascade) var bucketList: [BucketListItem]? = [] @Relationship(.cascade) var livingAccommodation: LivingAccommodation? }
-
4:54 - Transient properties
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date @Relationship(.cascade) var bucketList: [BucketListItem]? = [] @Relationship(.cascade) var livingAccommodation: LivingAccommodation? @Transient var tripViews: Int = 0 }
-
7:12 - Defining versioned schemas
enum SampleTripsSchemaV1: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... } enum SampleTripsSchemaV2: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { @Attribute(.unique) var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... } enum SampleTripsSchemaV3: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... }
-
7:49 - Implementing a SchemaMigrationPlan
enum SampleTripsMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [SampleTripsSchemaV1.self, SampleTripsSchemaV2.self, SampleTripsSchemaV3.self] } static var stages: [MigrationStage] { [migrateV1toV2, migrateV2toV3] } static let migrateV1toV2 = MigrationStage.custom( fromVersion: SampleTripsSchemaV1.self, toVersion: SampleTripsSchemaV2.self, willMigrate: { context in let trips = try? context.fetch(FetchDescriptor<SampleTripsSchemaV1.Trip>()) // De-duplicate Trip instances here... try? context.save() }, didMigrate: nil ) static let migrateV2toV3 = MigrationStage.lightweight( fromVersion: SampleTripsSchemaV2.self, toVersion: SampleTripsSchemaV3.self ) }
-
8:40 - Configuring the migration plan
struct TripsApp: App { let container = ModelContainer( for: Trip.self, migrationPlan: SampleTripsMigrationPlan.self ) var body: some Scene { WindowGroup { ContentView() } .modelContainer(container) } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.