SwiftData ์์ธํ ์ดํด๋ณด๊ธฐ WWDC ์์ ๋ฒ์ญ/์ ๋ฆฌ๋ณธ์ ๋๋ค.
- ๋ชฉ์ฐจ
- 00:00 Intro
- 03:42 Configuring persistence | ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๋๋ก ๋ชจ๋ธ์ ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ
- 07:21 Track and persist changes | ๋ณ๊ฒฝ ์ฌํญ์ ์ถ์ ํ๊ณ ์ง์ํ๋ ๋ฐฉ๋ฒ(ModelContext)
- 11:20 Modeling at scale | ๊ฐ์ฒด๋ฅผ ๋ค๋ฃฐ ๋ SwiftData๋ฅผ ์ต๋ํ ํ์ฉํ๋ ๋ฐฉ๋ฒ
- 14:54 Wrap-up
- doc
- ์ ์์ฒญ
- SwiftData ์์๋ณด๊ธฐ
- SwiftData๋ก ์คํค๋ง ๋ชจ๋ธ๋งํ๊ธฐ
0. Intro
์ด๋ฒ์๋ SampleTrips ์ฑ์ ํตํด SwiftData์ ๋ํด ์์๋ณผ ๊ฒ์ ๋๋ค. SampleTrips๋ ์ฌํํ ์๊ธฐ์ ์ฅ์๋ฅผ ์ฝ๊ฒ ์ ๋ฆฌํ ์ ์๋ ์ฑ์ ๋๋ค. SwiftData๋ฅผ ํ์ฉํ๋ฉด ์คํ ์ทจ์๋ ์ฑ ์ ํ ์ ์๋ ์ ์ฅ ๋ฑ ํ์ค ํ๋ซํผ ๊ธฐ๋ฅ์ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
Model
Types you already use
- @Model macro
- Inferred or explicit structure
- Deep customization
SwiftData๋ Swift๋ก ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๋ ์๋ก์ด ๋ฐฉ๋ฒ์ ๋๋ค. Class๋ Struct ๋ฑ ๊ธฐ์กด์ ์ฌ๋ฌ ํ์ ๋ค๊ณผ ํจ๊ป ํ์ฉํ ์ ์์ต๋๋ค.
์ด SwiftData์ ํต์ฌ์ ์๋ก์ด ๋งคํฌ๋ก @Model ์ ๋๋ค. ์ด๋ ์ฑ์์ ์ ์งํด์ผ ํ๋ ํ์ ์ SwiftData์ ์๋ ค ์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
์๋๋ SampleTrips ์ฑ์ Trip ํด๋์ค์ ๋๋ค. ์ฌํ ์ ๋ณด๋ฅผ ๊ฐ์ง๋ ๋ช ๊ฐ์ง ํ๋กํผํฐ์ SampleTrips ์ฑ์์ ์ฐ์ด๋ ๋ค๋ฅธ ๊ฐ์ฒด์ ๋ํ ์ฐธ์กฐ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
class Trip {
var destination: String?
var end_date: Date?
var name: String?
var start_date: Date?
var bucketListItem: [BucketListItem] = [BucketListItem]()
var livingAccommodation: LivingAccommodation?
}
์ด๋ ์๋์ฒ๋ผ ์์ ํ์ฌ SwiftData์ Model๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
@Model // << (1)SwiftData๋ก ์ฌ์ฉํ๊ธฐ ์ํด @Model ๋งคํฌ๋ก ์ถ๊ฐ
final class Trip {
var destination: String?
var end_date: Date?
var name: String?
var start_date: Date?
@Relationship(.cascade) // << (2)๊ด๊ณ ์ถ๊ฐ
var bucketListItem: [BucketListItem] = [BucketListItem]()
@Relationship(.cascade) // << (3)๊ด๊ณ ์ถ๊ฐ
var livingAccommodation: LivingAccommodation?
}
SwiftData๋ ์ฒ์์ ์ฝ๋์ฒ๋ผ ์ง์์ฑ์์ด ์์ฑํ๋ ์ฝ๋์ ๋๋ฒ์งธ๋ก ๋ณด์ฌ๋๋ฆฐ ์ง์์ฑ์ด ํ์ํ ์ฝ๋ ์ฌ์ด์ ์ฐจ์ด๋ฅผ ์ต์ํํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ฝ๊ฐ์ ์์ ์ผ๋ก SwiftData์ ํด๋น Trip ํด๋์ค๊ฐ ์ ์งํ๊ณ ์ถ์ ๋ชจ๋ธ์์ ์๋ ธ๊ณ , BucketListItem ๋ฐ LivingAccommodations์์ ๊ด๊ณ๊ฐ ์ด๋ป๊ฒ ๋์ํด์ผ ํ๋์ง ์์ฑํ์ต๋๋ค.
SwiftData๋ ์์ฑ๋ ์ฝ๋์์ ์ฌ๋ฌ๋ถ์ด ์ํ๋ ๊ตฌ์กฐ๋ฅผ ์๋์ผ๋ก ์ถ๋ก ํ ์๋ ์์ต๋๋ค. ๋ํ, ๊ฐ๋ ฅํ ์ฌ์ฉ์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ์ํ๋ ๋ฐ์ดํฐ ์ ์ฅ ๋ฐฉ์์ ์ ํํ ์์ ํ ์ ์๊ฒ ํด์ค๋๋ค.
@Model์ ์ญํ
@Model ์ด๋ ธํ ์ด์ ์ ํตํด Trip ํด๋์ค๋ SwiftData์์ ๋ ๊ฐ์ง ์ค์ํ ์ญํ ์ ๋งก์ต๋๋ค.
- Describe the schema
- Instances used in code
์ฒซ ๋ฒ์งธ๋ ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ฐ์ฒด ๊ทธ๋ํ์ธ ์คํค๋ง๋ฅผ ์์ ํ๋ ๊ฒ์ด๊ณ , ๋ ๋ฒ์งธ๋ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ ์ธํฐํ์ด์ค๊ฐ ๋๋ ๊ฒ์ ๋๋ค. ์ฆ ๋ ์ญํ ์ ์ํํ๋ ๋ฅ๋ ฅ์ด @Model ๋งคํฌ๋ก๊ฐ ๋ฌ๋ฆฐ ํด๋์ค๋ฅผ SwiftData๋ฅผ ์ฌ์ฉํ๋ ์ฑ์ ์ ์ด ์ค์ฌ์ ์ผ๋ก ๋ง๋ญ๋๋ค.
๊ฐ ์ญํ ์ ์ง์ํ๋ API ๊ฐ๋ ์ด ์์ต๋๋ค.
์คํค๋ง๋ ModelContainer๋ผ๋ ํด๋์ค์ ์ ์ฉ๋์ด ๋ฐ์ดํฐ์ ์ง์ ๋ฐฉ์์ ์์ ํฉ๋๋ค.
ModelContainer๋ ์คํค๋ง๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ธ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ๋ณด์ ํ ์ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์์ฑํฉ๋๋ค.
์ฝ๋์์ ๋ชจ๋ธ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ๋ค๋ฃฐ ๋๋ ํด๋น ์ธ์คํด์ค๊ฐ ModelContext์ ์ฐ๊ฒฐ๋ฉ๋๋ค. ModelContext๋ ๋ฉ๋ชจ๋ฆฌ์์ ์ธ์คํด์ค ์ํ๋ฅผ ์ถ์ ํ๊ณ ๊ด๋ฆฌํฉ๋๋ค.
์ด๋ SwiftData์ ํต์ฌ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
1. Configuring persistence
์ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๋๋ก ๋ชจ๋ธ์ ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ
์ง์์ฑ์ ๊ตฌ์กฐ๋ฅผ ์ค๋ช ํ๋ ๋ชจ๋ธ์ ์ฒซ ๋ฒ์งธ ์ญํ ์ ์ดํด๋ณด๊ณ , ModelContainer์ ํจ๊ป ์๋ํ๋ ๋ฐฉ์์ ์์๋ด ์๋ค.
Model Container
- ModelContainer๋ก ์คํค๋ง๋ฅผ ์ง์์ฑ๊ณผ ํตํฉํ๋ ๋ฒ
ModelContainer๋ ๋ฐ์ดํฐ๊ฐ ๊ธฐ๊ธฐ์์ ์ ์ฅ๋๊ณ ์ง์๋๋ ๋ฐฉ์์ ์์ ํฉ๋๋ค. ModelContainer๋ฅผ ์คํค๋ง์ ์คํค๋ง์ ์ง์์ฑ์ ์๋ ๋ค๋ฆฌ๋ก ์๊ฐํ ์ ์์ต๋๋ค. ๊ฐ์ฒด๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ๋์คํฌ ์ค ์ด๋์ ์ ์ฅ๋๋์ง ๋ฑ์ ์ ์ฅ ๋ฐฉ์์ ๋ํ ์์ ์ด ๋ฒ์ ๊ด๋ฆฌ์ ๋ง์ด๊ทธ๋ ์ด์ ๊ทธ๋ํ ๋ถ๋ฆฌ ๊ฐ์ ์คํ ๋ฆฌ์ง์ ์ด์ ๋ฐ ๊ฐ๋ฐ ์๋งจํฑ๊ณผ ๋ง๋๋ ๊ณณ์ ๋๋ค.
- Schema and persistence
- How objects are stored
- Evolution of models
- Versioning
- Migration
- Graph separation
์คํค๋ง๋ก ์ปจํ ์ด๋๋ฅผ ์ธ์คํด์คํํ๋ ๊ฑด ์ฝ์ต๋๋ค. ์์ ํ๋ ค๋ ํ์ ๋ง ์์ฑํ๋ฉด SwiftData๊ฐ ๋๋จธ์ง ์คํค๋ง๋ฅผ ์ฒ๋ฆฌํด ์ค๋๋ค. ์๋ฅผ ๋ค์ด Trip ํด๋์ค๊ฐ ๋ค๋ฅธ ๋ชจ๋ธ ์ ํ๊ณผ ๊ด๋ จํ๋ฏ๋ก ModelContainer๊ฐ ์ด ์คํค๋ง๋ฅผ ์ถ๋ก ํฉ๋๋ค.
// ModelContainer initialized with just Trip
let container = try ModelContainer(for: Trip.self)
// SwiftData infers related model classes as well
let container = try ModelContainer(for: [Trip.self, BucketListItem.self, LivingAccommodation.self])
ModelContainer์๋ ๊ฐ๋ ฅํ ์ด๋์ ๋ผ์ด์ ๊ฐ ๋ง์ผ๋ฉฐ, ModelConfiguration์ด๋ผ๋ ํด๋์ค๋ฅผ ์ฌ์ฉํด ๋์ฑ ๋ณต์กํ๊ฒ ๊ตฌ์ฑํ ์๋ ์์ต๋๋ค.
ModelConfiguration
- ModelConfiguration์ ์ฌ์ฉํ์ฌ ์ง์์ฑ ๊ธฐ๋ฅ์ ํ์ฑํํ๋ ๋ฐฉ๋ฒ
ModelConfiguration์ ์คํค๋ง์ ์ง์์ฑ์ ์์ ํฉ๋๋ค. ๋ฐ์ดํฐ์ ์ ์ฅ ์์น๋ฅผ ์ ์ดํฉ๋๋ค. ์ผ์ ๋ฐ์ดํฐ๋ ๋ฉ๋ชจ๋ฆฌ์ ์๊ตฌ ๋ฐ์ดํฐ๋ ๋์คํฌ์ ์ ์ฅํ๋ ์์ ๋๋ค. ModelConfiguration์ ์ฌ๋ฌ๋ถ์ด ์ ํํ ํน์ ํ์ผ URL์ ์ฐ๊ฑฐ๋ ๊ทธ๋ฃน ์ปจํ ์ด๋ ๊ถํ ๋ฑ ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ถํ์ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก URL์ ์์ฑํฉ๋๋ค. ๋ํ, ์๊ตฌ ํ์ผ์ด ์ฝ๊ธฐ ์ ์ฉ ๋ชจ๋๋ก ๋ก๋๋์ด์ผ ํจ์ ์์ ํ์ฌ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ ํ ํ๋ฆฟ ๋ฐ์ดํฐ์ ๋ํ ์์ฑ์ ๋ฐฉ์งํ ์๋ ์์ต๋๋ค. ๋ง์ง๋ง์ผ๋ก ๋ ๊ฐ ์ด์์ CloudKit ์ปจํ ์ด๋๋ฅผ ์ฐ๋ ์ฑ์ ์ด๋ฅผ ์คํค๋ง ModelConfiguration์ ์ผ๋ถ๋ก ์ง์ ํ ์ ์์ต๋๋ค.
Describes persistence of a schema
- In memory or on disk
- File location
- Read only
- CloudKit container identifier
์๋ก์ด Person ํด๋์ค์ Address ํด๋์ค๋ฅผ ์ฌ์ฉํด SampleTrips์ ์ฐ๋ฝ์ฒ ์ ๋ณด๋ฅผ ์ถ๊ฐํด๋ด ์๋ค.
๐๐ป ModelConfiguration์ ๊ธฐ๋ฅ์ผ๋ก ์ฑ์ ์ง์์ฑ ์๊ตฌ ์ฌํญ์ ๊ฐ๋จํ ์์ฑํ ์ ์์ต๋๋ค.
// 1. ์ฌ์ฉํ ์ ํ์ด ๋ชจ๋ ํฌํจ๋ ์ ์ฒด ์คํค๋ง๋ฅผ ์ ์ธ
let fullSchema = Schema([Trip.self, BucketListItem.self, LivingAccommodations.self, Person.self, Address.self])
// 2. ModelConfiguration์ ์ ์ธ(Trip๊ณผ BucketListItem LivingAccommodations ๋ชจ๋ธ์ ํฌํจ)
let trips = ModelConfiguration(
schema: Schema([Trip.self, BucketListItem.self, LivingAccommodations.self]),
url: URL(filePath: "/path/to/trip.store"), // ํด๋น ๊ฐ์ฒด ๊ทธ๋ํ์ ๋ฐ์ดํฐ ์ ์ฅ์ ์ฌ์ฉํ ํ์ผ์ URL์ ์ ์ธ
cloudKitContainerIdentifier: "com.example.trips" // SampleTrips ๋ฐ์ดํฐ๋ฅผ CloudKit์ ๋๊ธฐํํ ๋ ์ฌ์ฉํ CloudKit ์ปจํ
์ด๋์ ์ปจํ
์ด๋ ์๋ณ์ ์ ์ธ
)
// 3. Person ๋ฐ Address๊ฐ ํฌํจ๋ ์ ์คํค๋ง์ ๋ชจ๋ธ์ ModelConfiguration์ ์ ์ธ
// CloudKit ์ปจํ
์ด๋ ์๋ณ์์ ๊ณ ์ ํ ํ์ผ URL๊ณผ ํจ๊ป ์ ์ธ >> Trips ๊ทธ๋ํ์์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฆฌํ ์ ์๊ฒ ๋จ
let people = ModelConfiguration(
schema: Schema([Person.self, Address.self]),
url: URL(filePath: "/path/to/people.store"),
cloudKitContainerIdentifier: "com.example.people"
)
// 4. ์คํค๋ง์ ๊ตฌ์ฑ(configuration)์ ๊ฒฐํฉํ์ฌ ModelContainer๋ฅผ ํ์ฑ
let container = try ModelContainer(for: fullSchema, trips, people)
์๋์ผ๋ก ์ปจํ ์ด๋๋ฅผ ์ธ์คํด์คํํ๋ ๊ฒ๋ฟ๋ง ์๋๋ผ SwiftUI์ ์๋ก์ด ModelContainer ๋ชจ๋ํ์ด์ด๋ก ์ํ๋ ์ปจํ ์ด๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ๋ชจ๋ํ์ด์ด๋ ์์ฉ ํ๋ก๊ทธ๋จ์ ์ด๋ค ๋ทฐ๋ ์ฌ์๋ ์ถ๊ฐ ๊ฐ๋ฅํ๋ฉฐ ๊ฐ๋จํ ๊ฒ๋ถํฐ ๊ฐ๋ ฅํ ๊ฒ๊น์ง ๋ค์ํ ModelContainer๋ฅผ ์ง์ํฉ๋๋ค.
@main
struct TripsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
@main
struct TripsApp: App {
let fullSchema = Schema([Trip.self, BucketListItem.self, LivingAccommodations.self, Person.self, Address.self])
let trips = ModelConfiguration(
schema: Schema([Trip.self, BucketListItem.self, LivingAccommodations.self]),
url: URL(filePath: "/path/to/trip.store"),
cloudKitContainerIdentifier: "com.example.trips"
)
let people = ModelConfiguration(
schema: Schema([Person.self, Address.self]),
url: URL(filePath: "/path/to/people.store"),
cloudKitContainerIdentifier: "com.example.people"
)
let container = try ModelContainer(for: fullSchema, trips, people)
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
2. Track and persist changes
๋ณ๊ฒฝ ์ฌํญ์ ์ถ์ ํ๊ณ ์ง์ํ๋ ๋ฐฉ๋ฒ(ModelContext)
Model๊ณผ ModelContext๋ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฅผ ์์ฑํ๊ฑฐ๋ ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ ๋ ๊ฐ์ฅ ์์ฃผ ์ฐ์ด๋ ๊ฐ๋ ์ ๋๋ค. ์ด๋ฒ์๋ ModelContext๊ฐ ์ด๋ป๊ฒ ๋ณ๊ฒฝ ์ฌํญ์ ์ถ์ ํ๊ณ , ModelContainer๋ก ์์ ๋ ๋ด์ฉ์ ์ ์งํ๋์ง ์์๋ณผ ๊ฒ์ ๋๋ค.
@main
struct TripsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container) // << modelContainer ๋ชจ๋ํ์ด์ด
}
}
struct ContentView: View {
@Query var trips: [Trip] // << ์ฌํ ์ฟผ๋ฆฌ
@Environment(\.modelContext) var modelContext
var body: some View {
NavigationStack (path: $path) {
List(selection: $selection) {
ForEach(trips) { trip in
TripListItem(trip: trip)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
modelContext.delete(trip) // << ๋ฐ์ดํฐ ์ญ์
} label: {
Label("Delete", systemImage: "trash")
}
}
}
.onDelete(perform: deleteTrips(at:))
}
}
}
}
๋ทฐ๋ ์ฌ ์ฝ๋์์ modelContainer ๋ชจ๋ํ์ด์ด๋ฅผ ์ฌ์ฉํ๋ฉด ํน์ ํ ๋ฐฉ์์ผ๋ก ์ฑ ํ๊ฒฝ์ ์ค๋นํฉ๋๋ค. ๋ชจ๋ํ์ด์ด๋ @Environment์ ์ modelContext ํค๋ฅผ ์ปจํ ์ด๋์ mainContext์ ๋ฐ์ธ๋ฉํฉ๋๋ค. MainContext๋ ๋ทฐ๋ ์ฌ์์ ModelObjects์ ํจ๊ป ์์ ํ๊ธฐ ์ํ ํน๋ณํ MainActor-aligned model context์ ๋๋ค. @Environment์์ ๋ชจ๋ธ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ทฐ์ ์ฝ๋๊ฐ ์ฟผ๋ฆฌ์์ ์ฌ์ฉํ๋ ์ปจํ ์คํธ์ ์ฝ๊ฒ ์ก์ธ์คํ ์ ์์ผ๋ฉฐ, ์ญ์ ๊ฐ์ ์์ ์ ์ํํ ์ ์๊ฒ ๋ฉ๋๋ค.
๊ทธ๋์ ๋ชจ๋ธ ์ฝํ ์คํธ๋ ์ฌ์ฉ๊ณผ ์ก์ธ์ค๊ฐ ์ฝ์ง๋ง ์ค์ ์ญํ ์ ๋ญ๊น์?
๐๐ป ModelContext๋ ์์ฉ ํ๋ก๊ทธ๋จ์ด ๊ด๋ฆฌํ๋ ๋ฐ์ดํฐ์ ๋ํ ๋ทฐ๋ก ์๊ฐํ ์ ์์ต๋๋ค.
์์ ํ ๋ฐ์ดํฐ๋ ์ฌ์ฉ ์ค์ธ ๋ชจ๋ธ ์ฝํ ์คํธ๋ก ํ์นญ๋ฉ๋๋ค. SampleTrips์ ๊ฒฝ์ฐ ์์ ๋ ์ฌํ ๋ทฐ๊ฐ ๋ฆฌ์คํธ์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ ๋ ๊ฐ ์ฌํ ๊ฐ์ฒด๋ฅผ ๋ฉ์ธ ์ฝํ ์คํธ๋ก ๊ฐ์ ธ์ต๋๋ค. ์ฌํ์ ์์ ํ๋ฉด ํด๋น ๋ณ๊ฒฝ ์ฌํญ์ด ์ค๋ ์ท์ผ๋ก ๋ชจ๋ธ ์ฝํ ์คํธ์ ๊ธฐ๋ก๋ฉ๋๋ค. ์ ์ฌํ์ ์ถ๊ฐํ๊ฑฐ๋ ๊ธฐ์กด ์ฌํ์ ์ญ์ ํ๋ ๋ฑ ๋ค๋ฅธ ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ํ๋ฉด ์ฝํ ์คํธ๊ฐ ์ด๋ฌํ ๋ณ๊ฒฝ์ ์ํ๋ฅผ ์ถ์ ํ๊ณ ์ ์งํ๋ค๊ฐ 'context.save()'๋ฅผ ํธ์ถํ๋ฉด ๋ฉ์ถฅ๋๋ค. ์ญ์ ๋ ์ฌํ์ด ๋ฆฌ์คํธ์์ ์ฌ์ฉ์์๊ฒ ๋ณด์ด์ง ์๋๋ผ๋ ์ ์ฅ์ ํธ์ถํ์ฌ ํด๋น ์ญ์ ๋ฅผ ์ง์ํ๊ธฐ ์ ๊น์ง๋ ModelContext์ ๋จ์ ์๋ค๋ ๋ป์ ๋๋ค. save()๊ฐ ํธ์ถ๋๋ฉด ์ฝํ ์คํธ๊ฐ ๋ณ๊ฒฝ ์ฌํญ์ ModelContainer์ ์ง์์ํค๊ณ ์ํ๋ฅผ ์ง์๋๋ค.
๋ฆฌ์คํธ์ ํ์ํ๋ ๋ฑ ์ฝํ ์คํธ์์ ๊ฐ์ฒด๋ฅผ ๊ณ์ ์ฐธ์กฐํ๋ค๋ฉด ํด๋น ๊ฐ์ฒด๋ ์ฌ์ฉ์ด ๋๋ ๋๊น์ง ์ฝํ ์คํธ์ ๋จ๊ฒ ๋ฉ๋๋ค. ๊ทธ ์์ ์ ๊ฐ์ฒด๊ฐ ํด์ ๋๊ณ ์ฝํ ์คํธ๊ฐ ๋น์์ง๋๋ค.
ModelContext
ModelContext๋ ๋ฐ์ธ๋ฉ๋ ModelContainer์ ํจ๊ป ์๋ํฉ๋๋ค. ๋ทฐ์์ ๊ฐ์ ธ์จ ๊ฐ์ฒด๋ฅผ ์ถ์ ํ๊ณ ์ ์ฅ์ด ์คํ๋ ๋ ๋ณ๊ฒฝ ์ฌํญ์ ์ ํํฉ๋๋ค. ๋ํ ModelContext๋ ๋กค๋ฐฑ๊ณผ ์ฌ์ค์ ๊ฐ์ ๊ธฐ๋ฅ๋ ์ง์ํด ํ์ํ ๊ฒฝ์ฐ ์บ์ฑ๋ ์ํ๋ฅผ ์ง์ธ ์ ์์ต๋๋ค. ๊ทธ๋์ ์คํ ์ทจ์ ๋ฐ ์๋ ์ ์ฅ ๋ฑ์ ๊ธฐ๋ฅ์ ์ง์ํ๊ธฐ ๋๋ฌธ์ ์ด์์ ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค :)
- Track objects in use
- Propagates changes to ModelContainer
- Clear changes with rollback or reset
@main
struct TripsApp: App {
@Environment(\.undoManager) var undoManager
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Trip.self, isUndoEnabled: true)
}
}
SwiftUI์์ ModelContainer ๋ชจ๋ํ์ด์ด๋ isUndoEnabled argument๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์๋์ฐ์ undoManager๋ฅผ ์ปจํ ์ด๋์ mainContext์ ๋ฐ์ธ๋ฉํ๋ ์ธ์์ ๋๋ค. ๋ค์ ๋งํด ๋ฉ์ธ ์ฝํ ์คํธ์ ๋ณ๊ฒฝ ์ฌํญ์ด ์๊ธฐ๋ฉด ์ธ ์๊ฐ๋ฝ์ผ๋ก ๋ฐ๊ธฐ๋ ํ๋ค๊ธฐ ๋ฑ์ ์์คํ ์ ์ค์ฒ๋ก ์ถ๊ฐ ์ฝ๋ ์์ด ๋ณ๊ฒฝ์ ์คํ ์ทจ์ ํน์ ์คํ ๋ณต๊ท ํ ์ ์์ต๋๋ค.
Undo with ModelContext
๋ชจ๋ธ ๊ฐ์ฒด์ ๋ณ๊ฒฝ์ด ๋ฐ์ํ๋ฉด ModelContext๋ ์๋์ผ๋ก ์คํ ์ทจ์ ๋ฐ ์คํ ๋ณต๊ท ๋์์ ๋ฑ๋กํฉ๋๋ค. ModelContainer ๋ชจ๋ํ์ด์ด๊ฐ ์ฐ๋ ํ๊ฒฝ์ undoManager๋ ์๋์ฐ๋ ์๋์ฐ ๊ทธ๋ฃน์ ์ผ๋ถ๋ก ์์คํ ์์ ์ ๊ณต๋ฉ๋๋ค. ๊ทธ๋์ ์ธ ์๊ฐ๋ฝ์ผ๋ก ๋ฐ๊ธฐ๋ ํ๋ค๊ธฐ ๊ฐ์ ์์คํ ์ ์ค์ฒ๊ฐ ์์ฉ ํ๋ก๊ทธ๋จ์์ ์๋์ผ๋ก ์๋ํฉ๋๋ค.
- Automatically registers actions
- modelContainer() the environment’s undoManager
- Supports standard system gestures
ModelContext Autosave
ModelContext๊ฐ ์ง์ํ๋ ๋ ๋ค๋ฅธ ํ์ค ์์คํ ๊ธฐ๋ฅ์ ๋ฐ๋ก ์๋ ์ ์ฅ์ ๋๋ค. ์๋ ์ ์ฅ์ด ํ์ฑํ๋๋ฉด ๋ชจ๋ธ ์ฝํ ์คํธ๋ ์์คํ ์ด๋ฒคํธ์ ์ ์ฅ๋ฉ๋๋ค. ํฌ๊ทธ๋ผ์ด๋๋ ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ์์ฉ ํ๋ก๊ทธ๋จ์ด ์ ํ๋๋ ๋ฑ์ ์ด๋ฒคํธ์ ๋๋ค.
- Main context automatically saves
- System events
- Periodically as app is used
@main
struct TripsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Trip.self, isAutosaveEnabled: false) // <-- ์๋์ ์ฅ ๋นํ์ฑํ
}
}
๋ฉ์ธ ์ฝํ ์คํธ๋ ์์ฉ ํ๋ก๊ทธ๋จ์ด ์ฐ์ด๋ ๋์ ์ฃผ๊ธฐ์ ์ผ๋ก ์ ์ฅ๋ฉ๋๋ค. ์๋ ์ ์ฅ์ ์์ฉ ํ๋ก๊ทธ๋จ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฑํ๋๋ฉฐ ํ์์ ModelContainer ์์ ์์ isAutosaveEnabled ์ธ์๋ก ๋นํ์ฑํํ ์ ์์ต๋๋ค.
์๋์ผ๋ก ๋ง๋ ๋ชจ๋ธ ์ฝํ ์คํธ์์ ์๋ ์ ์ฅ์ด ๋นํ์ฑํ๋ฉ๋๋ค.
3. Modeling at scale
๊ฐ์ฒด๋ฅผ ๋ค๋ฃฐ ๋ SwiftData๋ฅผ ์ต๋ํ ํ์ฉํ๋ ๋ฐฉ๋ฒ
์์ฉ ํ๋ก๊ทธ๋จ์ด ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ณณ์ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฟ๋ง์ด ์๋๋๋ค. ์ด๋ฒ์๋ SwiftData๋ก ์ฝ๊ณ ์์ ํ๊ฒ ๊ฐ๋ ฅํ๊ณ ํ์ฅ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฒ์ ์์๋ด ์๋ค.
Model Context at Scale
๋ฐฑ๊ทธ๋ผ์ด๋ ํ์์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ์์ ๊ณผ ์๊ฒฉ ์๋ฒ๋ ๋ค๋ฅธ ์๊ตฌ ๋ฉ์ปค๋์ฆ๊ณผ์ ๋๊ธฐํ ๋ฐฐ์น ์ฒ๋ฆฌ๋ ๋ชจ๋ ์ฃผ๋ก ์งํฉ์ด๋ ๊ทธ๋ํ ํํ๋ก ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ๋๋ฐํฉ๋๋ค.
- Background operations
- Sync
- Batch Processing
let context = self.newSwiftContext(from: Trip.self)
var trips = try context.fetch(FetchDescriptor<Trip>())
์ด ์ค ๋ง์ ์์ ์ด ModelContext์ ํ์น ๋ฉ์๋๋ก ์์ ํ ๊ฐ์ฒด ์งํฉ์ ๊ฐ์ ธ์ต๋๋ค.
์ด ์์ ์์๋ Trip ๋ชจ๋ธ์ FetchDescriptor๋ก ์ฌํ ์ด๋ ์ด๊ฐ Trip ๊ฐ์ฒด ์ปฌ๋ ์ ์์ Swift์ ์๋ฆฝ๋๋ค. ์บ์คํ ์ด๋ ๋ณต์กํ ๊ฒฐ๊ณผ ํฌํ์ ๊ฑฑ์ ํ ํ์๊ฐ ์์ต๋๋ค.
let context = self.newSwiftContext(from: Trip.self)
let hotelNames = ["First", "Second", "Third"]
var predicate = #Predicate<Trip> { trip in
trip.livingAccommodations.filter {
hotelNames.contains($0.placeName)
}.count > 0
}
var descriptor = FetchDescriptor(predicate: predicate)
var trips = try context.fetch(descriptor)
์๋ก์ด Predicate ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํด์ FetchDescriptor๋ก ์์ฝ๊ฒ ๋ณต์กํ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ํน์ ํธํ ์์ ์๋ฐํ๋ ์ฌํ์ ์ฐพ๊ฑฐ๋ ํ๋์ ์์ฝํด์ผ ํ๋ ์ฌํ์ ์ฐพ์ ์ ์์ต๋๋ค. SwiftData์์๋ ํ์ ์ฟผ๋ฆฌ์ ์กฐ์ธ์ ์ง์ํ๋ ๋ณต์กํ ์ฟผ๋ฆฌ๋ ๋ชจ๋ ์์ํ๊ฒ Swift๋ก ์์ฑํ ์ ์์ต๋๋ค.
let context = self.newSwiftContext(from: Trip.self)
predicate = #Predicate<Trip> { trip in
trip.livingAccommodations.filter {
$0.hasReservation == false
}.count > 0
}
descriptor = FetchDescriptor(predicate: predicate)
var trips = try context.fetch(descriptor)
Predicate๋ ์ฌ๋ฌ๋ถ์ด ๋ง๋ ๋ชจ๋ธ์ ์ฐ๊ณ SwiftData๋ ๊ทธ ๋ชจ๋ธ์์ ์์ฑ๋ ์คํค๋ง๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฌํ ์์ ๋ถ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ๋ก ๋ณํํฉ๋๋ค.
FetchDescriptor
FetchDescriptor๋ ์๋ก์ด Foundation Predicate ๋งคํฌ๋ก์ ๊ธฐ๋ฅ์ ์คํค๋ง์ ๊ฒฐํฉํ์ฌ ์ปดํ์ผ๋ฌ ์ธ์ฆ ์ฟผ๋ฆฌ๋ฅผ Apple ํ๋ซํผ์์ ์ง์์์ผ ์ค๋๋ค. FetchDescriptor์ SortDescriptor ๋ฑ ๊ด๋ จ ํด๋์ค๋ ์ ๋ค๋ฆญ์ผ๋ก ๊ฒฐ๊ณผ ์ ํ์ ํ์ฑํ๊ณ , ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ธ ํ๋กํผํฐ๋ฅผ ์ปดํ์ผ๋ฌ์ ์๋ ค ์ค๋๋ค. ์ฌ๋ฌ ๊ฐ์ง ์กฐ์ ์ต์ ์ธ offset๊ณผ limit faulting ๋ฐ prefetching ๋งค๊ฐ๋ณ์๊ฐ ์์ต๋๋ค.
- Compiler validated queries
- Typed by model
- Additional parameters
- Offset/limit
- Faulting/prefetching
์ด ๋ชจ๋ ๊ธฐ๋ฅ์ ModelContext์ enumerate ํจ์์์ ๊ฒฐํฉ๋ฉ๋๋ค. ์ด๋ single call site์์ ํ๋ซํผ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์บก์ํํ์ฌ ์๋ฌต์ ์ผ๋ก ๋ฐฐ์น ์ํ ๋ฐ ์ด๊ฑฐ ํจํด์ ๋ง๋ค ์ ์๋๋ก ์ค๊ณ๋์์ต๋๋ค.
context.enumerate(FetchDescriptor<Trip>()) { trip in
// Operate on trip
}
enumerate๋ FetchDescriptor๊ฐ ์๋ฌด๋ฆฌ ๋ณต์กํด๋ ํ๋ฅญํ ์๋ํฉ๋๋ค. ๊ฐ๋จํ ๊ฒ๋ถํฐ ๊ฐ๋ ฅํ ๊ฒ๊น์ง ๋ชจ๋ ์ง์ํฉ๋๋ค. enumerate๋ ๋ฐฐ์นญ์ด๋ mutation guards ๋ฑ์ ํ๋ซํผ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์๋์ผ๋ก ๊ตฌํํฉ๋๋ค. ์ด๋ ์ฌ์ฉ ์์ ๋ง๊ฒ ์ฌ์ฉ์ํํ ์ ์์ต๋๋ค.
let predicate = #Predicate<Trip> { trip in
trip.bucketListItem.filter {
$0.hasReservation == false
}.count > 0
}
let descriptor = FetchDescriptor(predicate: predicate)
descriptor.sortBy = [SortDescriptor(\.start_date)]
context.enumerate(descriptor) { trip in
// Remind me to make reservations for trip
}
์๋ฅผ ๋ค์ด enumerate ๋ฐฐ์น์ ๊ธฐ๋ณธ ํฌ๊ธฐ๋ ๊ฐ์ฒด 5์ฒ ๊ฐ์ ๋๋ค. ์ํ ์ค I/O ์์ ์ ์ค์ด๊ธฐ ์ํด ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ๋ฅผ ๊ฐ์ํ๊ณ 1๋ง์ผ๋ก ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. ์ด๋ฏธ์ง๋ ๋์์ ๊ธฐํ ๊ณ ์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋ ๋ฌด๊ฑฐ์ด ๊ฐ์ฒด ๊ทธ๋ํ์ ๊ฒฝ์ฐ ๋ ์์ ๋ฐฐ์น ํฌ๊ธฐ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฐฐ์น ํฌ๊ธฐ๋ฅผ ์ค์ด๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ๋ ๊ฐ์ํ์ง๋ง ์ด๊ฑฐ ์ค I/O๊ฐ ์ฆ๊ฐํฉ๋๋ค.
let predicate = #Predicate<Trip> { trip in
trip.bucketListItem.filter {
$0.hasReservation == false
}.count > 0
}
let descriptor = FetchDescriptor(predicate: predicate)
descriptor.sortBy = [SortDescriptor(\.start_date)]
context.enumerate(
descriptor,
batchSize: 10000 // << HERE !!!!
) { trip in
// Remind me to make reservations for trip
}
enumerate๋ ๊ธฐ๋ณธ์ ์ผ๋ก mutation guard๋ฅผ ํฌํจํฉ๋๋ค ๊ท๋ชจ๊ฐ ํฐ ์ํ์์ ๋ฐ์ํ๋ ์ฑ๋ฅ ๋ฌธ์ ๋ ๋๋ถ๋ถ ์ด๊ฑฐ ์ค ์ฝํ ์คํธ์ ๊ฐํ ๋ณํ ๋๋ฌธ์ ๋๋ค. allowEscapingMutations๋ก ์๋๋ ๊ฒฐ๊ณผ์์ enumerate์ ์๋ฆด ์ ์์ต๋๋ค. ์ค์ ํ์ง ์์ ๊ฒฝ์ฐ ์ด๊ฑฐ๋ฅผ ์ํํ๋ ModelContext๊ฐ ๋ํฐ๋ก ๋ฐ๊ฒฌ๋๋ฉด enumerate๊ฐ ์์ธ๋ฅผ ๋ฐ์์์ผ ์ด๋ฏธ ์ํํ ๊ฐ์ฒด์ ํด์ ๋ฅผ ๋ง์ต๋๋ค.
let predicate = #Predicate<Trip> { trip in
trip.bucketListItem.filter {
$0.hasReservation == false
}.count > 0
}
let descriptor = FetchDescriptor(predicate: predicate)
descriptor.sortBy = [SortDescriptor(\.start_date)]
context.enumerate(
descriptor,
batchSize: 500,
allowEscapingMutations: true // <-- HERE!!!
) { trip in
// Remind me to make reservations for trip
}
4. Wrap-up
์ด๋ฒ ์ธ์ ์์๋ ์คํค๋ง์ ModelConfiguration์ผ๋ก ํจ๊ณผ์ ์ธ ์ง์์ฑ์ ๊ตฌ์ฑํ๋ ๋ฒ์ ์์๋ดค์ต๋๋ค. ๋ํ ModelContainer์ ModelContext๋ก ์คํ ์ทจ์์ ์คํ ๋ณต๊ท ๋ฑ ํ์ค ์์คํ ๊ธฐ๋ฅ์ ์ฝ๊ฒ ์ ์ฉํ๋ ๋ฐฉ๋ฒ๋ ๋ฐฐ์ ์ต๋๋ค. ์ด์ FetchDescriptor์ enumerate๋ฅผ ํตํด ์ด์ ๋ณด๋ค ์์ ํ๊ณ ์ฐ์ํ ์ฝ๋๋ฅผ SwiftData์์ ์์ฑํ ์ ์์ต๋๋ค.
- Adopt Schema and ModelConfiguration
- Adopt undo and redo
- Adopt FetchDescriptor and enumerate
'๐ Apple > WWDC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[SwiftData] 02. Model your schema with SwiftData(SwiftData๋ก ์คํค๋ง ๋ชจ๋ธ๋งํ๊ธฐ) (0) | 2024.08.20 |
---|---|
[SwiftData] 01. Meet SwiftData (SwiftData ๋ง๋๋ณด๊ธฐ) (1) | 2024.08.14 |