Published on

๐ŸŽŠ WWDC23 - SwiftData

Authors
  • avatar
    Name
    ์ด์ฐฝ์ค€
    Twitter

WWDC23 - SwiftData

์˜ค๋Š˜์€ SwiftData๋ฅผ ์•Œ์•„๋ณผ๊ฑฐ์—์š” Widget์—๋‹ค ์จ๋จน์–ด์•ผ๋˜๊ฑฐ๋“ ์š” ๐Ÿ˜„

์ €๋Š” ์ผ๋‹จ CoreData๋ฅผ ์จ๋ณธ ์ ์ด ์—†์Šต๋‹ˆ๋‹ค. ์•„๋งˆ ์กฐ๋งŒ๊ฐ„ ์“ธ ๊ฒƒ ๊ฐ™๊ธฐ๋Š” ํ•œ๋ฐ.. ์ผ๋‹จ์€ ์—†์–ด์š”.. ์™œ ์•ˆ์ผ๋ƒ๋ฉด์š”.. ์Šฌ์ฉ ๋ด๋„ ํŒŒ์ผ์ด ๋„ˆ๋ฌด ์—ฌ๊ธฐ์ €๊ธฐ ๋ถ„์‚ฐ๋˜๊ฒŒ ๋˜๋Š” ๊ฒƒ ๊ฐ™๋”๋ผ๊ตฌ์š”.. ์ „์šฉ์˜ ํŒŒ์ผ ํ˜•์‹๋„ ์žˆ๊ณ  ๋ง์ด์ฃ โ€ฆ? ๊ทธ๋ž˜์„œ ๋Œ€์‹  Realm์„ ์ž์ฃผ ์ผ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ.. SwiftData๊ฐ€ ์ƒˆ๋กœ ๋‚˜์™”์–ด์š”! iOS 17๋ถ€ํ„ฐ ์ง€์›ํ•ด์„œ ์‹ค๋ฌด์—์„œ๋Š” 4~5๋…„ ๋’ค์—๋‚˜ ์“ฐ๊ฒŒ ๋  ๊ฒƒ ๊ฐ™์ง€๋งŒ ๋‚˜์˜จ๊ฒŒ ์–ด๋”˜๊ฐ€์š” ๐Ÿ˜‚ ์ผ๋‹จ Schema๋ฅผ ์ •์˜ํ•ด์ฃผ๋Š” ๊ณผ์ •๋ถ€ํ„ฐ ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

์ถœ๋ฐœํ•ด๋ณด์ฃ !

Using the Model Macro

SwiftData์˜ Schema ๋Š” ์ผ๋ฐ˜ ํด๋ž˜์Šค์— @Model ๋งคํฌ๋กœ ๋ฅผ ๋ถ™์—ฌ์ฃผ๋Š” ๊ฒƒ์œผ๋กœ ๋์ž…๋‹ˆ๋‹ค.

import SwiftData

@Model
class Trip {
  var name: String
  var destination: String
  var endDate: Date
  var startDate: Date

  var bucketList: [BucketListItem]? = []
  var livingAccommodation: LivingAccommodation?
}

๋„ˆ๋ฌด ์‰ฝ๊ณ  ๊ฐ„๋‹จํ•˜์ฃ ..?

@Model ๋งคํฌ๋กœ๋Š” ํด๋ž˜์Šค ๋‚ด๋ถ€์˜ Stored Property ๋“ค์„ Persisted Property ๋กœ ๋ฐ”๊ฟ”์ค๋‹ˆ๋‹ค.

์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…๋“ค์€ ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

  • ๊ธฐ๋ณธ์ ์ธ Value Types (Int, String, โ€ฆ)
  • struct
  • enum
  • Codable
  • [ANY VALUE TYPE]

๋‹ค๋ฅธ @Model ํด๋ž˜์Šค๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ด€๊ณ„ ๊ตฌ์กฐ๋ฅผ ํ˜•์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

  • [BucketListItem]? ์™€ ๊ฐ™์€..

SwiftData ํด๋ž˜์Šค์™€ ์‹ค์ œ ์‚ฌ์šฉ๋˜๋Š” ํด๋ž˜์Šค๋Š” ๋™์ผํ•œ๊ฐ€์š”?

๊ฐœ์ธ์ ์œผ๋กœ ๊ฐ€์žฅ ๊ถ๊ธˆํ•œ ์ ์ด์˜€์Šต๋‹ˆ๋‹ค.

public protocol Storable {
  associatedtype RealmObject: RealmSwift.Object
  associatedtype PropertyValue: PropertyValueType

  /// ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์˜ ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ”„๋กœํผํ‹ฐ
  var identifier: Int { get }

  init(realmObject: RealmObject)
  func realmObject() -> RealmObject
}

Realm์„ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋‹ˆ DB์—์„œ ์‚ฌ์šฉํ•˜๋Š” Data ๊ตฌ์กฐ์ฒด์™€ ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋„๋ฉ”์ธ ํ˜•ํƒœ์˜ ๊ตฌ์กฐ์ฒด๋Š” ๋‹ค๋ฅธ ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ณค ํ–ˆ๊ฑฐ๋“ ์š”.. (๋ณดํ†ต ๋ฉ€ํ‹ฐ ์“ฐ๋ ˆ๋”ฉ์˜ ๊ณผ์ •์—์„œ ๋ฌธ์ œ๋“ค์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.)

ํ•˜์ง€๋งŒ SwiftData๋Š” ์ €ํฌ๊ฐ€ ์›๋ž˜ ์‚ฌ์šฉํ•˜๋˜ ์ฝ”๋“œ์™€ ๋ณ„ ์ฐจ์ด๊ฐ€ ์—†๋„๋ก ์• ํ”Œ์ด ๋…ธ๋ ฅํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Model ํด๋ž˜์Šค๋ฅผ ์ง์ ‘์ ์œผ๋กœ UI ๋ ˆ์ด์–ด ๋“ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.


@Attribute ์™€ @Relationship ์œผ๋กœ ํ”„๋กœํผํ‹ฐ์— ์ถ”๊ฐ€์ ์ธ ์ œ์–ด๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Attribute

@Attribute(.unique) var name: String

@Attribute(.unique) ๋กœ PK๋ฅผ ์„ค์ •ํ•ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. PK๋กœ ์„ค์ •๋œ ํ”„๋กœํผํ‹ฐ์™€ ๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง„ ๋ชจ๋ธ์ด Create์— ๋Œ€์‘๋˜๋Š” insert()๋กœ ์ธํ•ด ์ถ”๊ฐ€๋  ๋•Œ Update์— ๋Œ€์‘๋˜๋Š” upsert()๋กœ ์ „ํ™˜๋˜๋Š” ํšจ๊ณผ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

@Attribute(originalName: "start_date") var startDate: Date

@Attribute(originalName)์œผ๋กœ๋Š” Migration์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœํผํ‹ฐ์˜ ์ด๋ฆ„์€ ๋ฐ”๊พธ๊ณ  ์‹ถ์ง€๋งŒ ๋ฐ์ดํ„ฐ๋Š” ๊ทธ๋Œ€๋กœ ๋‚จ๊ฒจ๋‘๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ ์™ธ์—๋„

  • ์™ธ๋ถ€ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
  • Transformable
  • Spotlight
  • Hash ์ˆ˜์ •

๋“ฑ์˜ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํ•˜๋„ค์š”.

@Relationship

๊ฐ ๋ชจ๋ธ๊ฐ„ Relationship์ด ์„ค์ •๋˜์–ด ์žˆ์„ ๋•Œ, ์‚ญ์ œ ์ •์ฑ…์˜ ๊ธฐ๋ณธ๊ฐ’์€ Nullify ์ž…๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋กœ ์‚ดํŽด๋ณด๋ฉด Trip์ด ์‚ญ์ œ๋์„๋•Œ, BucketListItem๊ณผ LivingAccommodation์˜ Inverse Relationship์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ๋˜ trip์ด๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๊ฐ€ NULL์ด ๋˜๋Š” ๊ฒƒ์ด์ฃ .

ํ•˜์ง€๋งŒ .cascade ์ •์ฑ…์œผ๋กœ Nullify ๋Œ€์‹  ํ•จ๊ป˜ ์‚ญ์ œ ๋˜๋„๋ก ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

@Relationship(.cascade) var bucketList: [BucketListItem]? = []
  • originalName
  • toMany Relationship์ผ ๋•Œ์˜ ๊ฐœ์ˆ˜ ์ œํ•œ
  • Hash ์ˆ˜์ •

@Transient

@Transient var tripViews: Int = 0

@Transient ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ๊ฐ€ SwiftData๋กœ ์“ฐ์ด๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€์‹  ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ๋Š” ๊ธฐ๋ณธ๊ฐ’ ์„ ๊ผญ ๊ฐ€์ ธ์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.

Evolving Schemas

์•ฑ์„ ๊ฐœ๋ฐœํ•˜๊ฑฐ๋‚˜ ์ƒ์šฉํ™”๋ฅผ ํ•œ ์ดํ›„์—๋„ DB์˜ ๊ตฌ์กฐ๋Š” ์–ธ์ œ๋“ ์ง€ ๋ฐ”๋€” ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋•Œ๋ฌธ์— Migration์€ ํ•„์ˆ˜์ ์ธ ์š”๊ฑด์ด์ฃ .

SwiftData๋„ VersionedSchema์™€ SchemaMigrationPlan์„ ํ†ตํ•ด ์ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

enum SampleTripSchemaV1: 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?
}
enum SampleTripSchemaV2: 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: "srart_date") var startDate: Date
    @Attribute(originalName: "end_date") var endDate: Date

    var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}

SchemaMigrationPlan์œผ๋กœ๋Š” ์ด๋ ‡๊ฒŒ ๋ฐ”๊พผ ๋ฐ์ดํ„ฐ๋“ค์„ ์–ด๋–ค ์ˆœ์„œ๋Œ€๋กœ ๋ณ€๊ฒฝํ• ์ง€ ๋“ฑ์˜ ์„ธ๋ถ€์ ์ธ ์‚ฌํ•ญ๋“ค์„ ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

enum SampleTripsMigrationPlan: SchemaMigrationPlan {
  static var schemas: [any VersionedSchema.Type) {
    [SampleTripsSchemaV1.self, SampletripsSchemaV2.self, SampleTripSchemaV3.self]

  static var stages: [MigrationStage] {
    [migrateV1toV2]
  }

  static let migrateV1toV2 = MigrationStage.custom(
    fromVersion: SampleTripSchemaV1.self
    toVersion: SampleTripSchema2.self    ,
    willMigrate: { context in
      let trips = try? context.fetch(FetchDescriptor<SampleTripsSchemaV1.Trip>())
      try? context.save()  
    }, didMigrate: nil
  )
}

Working with your Data

Model Container

Schema ๋Š” ๋‹จ์ˆœํžˆ โ€œ์–ด๋–ค ํ˜•ํƒœ๋กœ ์ €์žฅ๋˜๋Š” ์ง€์— ๋Œ€ํ•œ ๊ฐ€์ด๋“œ๋ผ์ธโ€ ์ •๋„์˜ ์—ญํ• ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฌด์–ธ๊ฐ€๋„ ์žˆ์–ด์•ผ๊ฒ ์ฃ ?

๊ทธ๊ฒƒ์ด ๋ฐ”๋กœ Model Container ์ž…๋‹ˆ๋‹ค.

Model Container๋Š” Schema๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ์ธ์Šคํ„ด์Šคํ™” ๋œ ๊ฐ ๋ฐ์ดํ„ฐ๋“ค(Persistence)์€ Model Context ๋ฅผ ํ†ตํ•ด์„œ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜์ฃ .

๊ทธ๋Ÿฌ๋‹ˆ๊นŒ Model Container ๋Š” Schema์™€ Persistence ์‚ฌ์ด์˜ ์—ฐ๊ฒฐ๋ชฉ ์—ญํ• ์„ ํ•˜๋Š”๊ฒ๋‹ˆ๋‹ค.

Model Container๋Š” ์ด๋Ÿฐ ์ž‘์—…๋“ค์„ ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฒ„์ „ ๊ด€๋ฆฌ
  • Migration
  • Graph ๋ถ„๋ฆฌ

Container ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

import SwiftData

let container = try ModelContainer(for: [Trip.self,
                                         BucketListItem.self,
                                         LiveAccommodation.self])  

๊ทธ๋ฆฌ๊ณ  ๋” ๋ณต์žกํ•œ ํ˜•ํƒœ์˜ DB๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”๊ฐ€์ ์ธ Configuration๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

import SwiftData

let container = try ModelContainer(for: [Trip.self, LivingAccommodation.self],
                                   configuration: ModelConfiguration(url: URL("path")))         

Model Configuration

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)    

ModelConfiguration ์„ ํ†ตํ•ด์„œ๋Š”

  • Schema์˜ Persistence๋ฅผ ์„ค๋ช…ํ•˜๊ฑฐ๋‚˜ (๋ฉ”๋ชจ๋ฆฌ ํ˜น์€ ๋””์Šคํฌ)
  • ์ €์žฅ๋˜๋Š” ์œ„์น˜๋ฅผ ์ง€์ •ํ•˜๊ฑฐ๋‚˜
  • Read-Only ๋ชจ๋“œ๋„ ์ง€์›ํ•˜๊ณ 
  • CloudKit์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ identifier๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์œ„ ์˜ˆ์‹œ์˜ ๊ฒฝ์šฐ trips์™€ people์˜ ๊ทธ๋ž˜ํ”„๋ฅผ ์ €์žฅ๋˜๋Š” ์œ„์น˜์™€ CloudKit์˜ identifier๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•ด์ฃผ๊ณ  ์žˆ๋„ค์š”.

์ดํ›„, ModelContainer ๋ฅผ ์ƒ์„ฑํ•  ๋–„ ๋งŒ๋“ค์–ด๋‘” Schema์™€ Graph๋“ค์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Model Context

Model Context ๋Š” Model์— ์ƒ๊ธฐ๋Š” ๋ณ€ํ™”๋ฅผ ๊ด€์ฐฐํ•˜๋‹ค๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š” ํ–‰๋™๋“ค์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

1. ์—…๋ฐ์ดํŠธ๋ฅผ ์ถ”์ ํ•˜๊ณ , 2. ๋ฐ์ดํ„ฐ๋ฅผ fetchํ•˜๊ณ , 3. ๋ณ€๊ฒฝ์ ์„ ์ €์žฅํ•˜๊ณ , 4. ๋ณ€๊ฒฝ์ ์„ ์ทจ์†Œํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

import SwiftData

let context = container.mainContext
let context = ModelContext(container)  

์ด๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ๊ฐ€๋Šฅํ•˜๋ƒ!? ํ•˜๋ฉด, ModelContext๋Š” ์ผ์ข…์˜ Snapshot ์ฒ˜๋Ÿผ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค Model์˜ Snapshot์„ ์ฐ์–ด ๊ทธ ๋•Œ์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ด๋‘๋Š” ๊ฒƒ์ด์ฃ .

๋•๋ถ„์— ์‚ญ์ œ, ์ˆ˜์ •๊ณผ ๊ฐ™์€ ๋ณ€ํ™”๊ฐ€ ์ด๋ฃจ์–ด์ ธ๋„ ์–ด๋–ค ์ธ์Šคํ„ด์Šค๊ฐ€ ์‚ญ์ œ๋๊ณ , ์ˆ˜์ •๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด ๋ฅผ ๋‹ด๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ฃผ์˜ํ•  ์ ์€ ModelContext์˜ ๋ณ€๊ฒฝ์ ๋“ค์€ context.save() ๋ฅผ ํ†ตํ•ด ์ €์žฅํ•˜์ง€ ์•Š์œผ๋ฉด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Undo with ModelContext

Model Container ์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ undoManager๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์„ธ ์†๊ฐ€๋ฝ์œผ๋กœ ์Šค์™€์ดํ”„, ํ”๋“ค์–ด์„œ ์ž…๋ ฅ ์ทจ์†Œ ๋“ฑ ์‹œ์Šคํ…œ์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ์ œ์Šค์ฒ˜๋“ค์„ ์ž๋™์œผ๋กœ ์ง€์›ํ•œ๋‹ค๋Š” ๊ทธ๋Ÿฐ ์–˜๊ธฐ์ž…๋‹ˆ๋‹ค..

ModelContext AutoSave

.modelContainer(for: Trip.self, isAutosaveEnabled: false)

Undo๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ž๋™ ์ €์žฅ๋„ ์ง€์›ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ๋กœ ๊ฐ€๊ฑฐ๋‚˜ ์ข…๋ฃŒ๋  ๋•Œ, ํ˜น์€ ์•ฑ์ด ์‚ฌ์šฉ๋จ์— ๋”ฐ๋ผ ์ž๋™์ ์œผ๋กœ Context๋ฅผ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

Fetchํ•œ ๊ฐ’๋“ค์€ ๊ธฐ์กด์— ์žˆ๋˜ SortDescriptor์™€ ์ƒˆ๋กœ์šด Predicate, FetchDescriptor๋ฅผ ์‚ฌ์šฉํ•ด ์ •๋ ฌํ•˜๊ฑฐ๋‚˜ ํ•„ํ„ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Predicate

let tripPredicate = #Predicate<Trip> { $0.destination == "New York" }
let tripPredicate = #Predicate<Trip> {
  $0.destination == "New York" &&
  $0.name.contains("birthday")
}

#Predicate ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•ด ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ธฐ์กด์˜ filter์™€ ๋น„์Šทํ•œ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์กด์˜ filter๋Š” .filter { }.filter { }์™€ ๊ฐ™์ด ์ค„์ค„์ด ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ–ˆ๋˜ ๋ฐ˜๋ฉด, ํ•˜๋‚˜์˜ ํด๋กœ์ € ๋‚ด๋ถ€์—์„œ ๋ชจ๋“  ์กฐ๊ฑด๋“ค์„ ๊ฒ€์‚ฌ ํ•  ์ˆ˜ ์žˆ๋Š” ์ ์ด ํŠน์ง•์ž…๋‹ˆ๋‹ค.

Fetch Descriptor

let descriptor = FetchDescriptor<Trip>(predicate: tripPredicate)

let trips = try context.fetch(descriptor)

FetchDescriptor๋Š” ์œ„์—์„œ ์‚ดํŽด๋ณธ Predicate๋ฅผ context์— ์ ์šฉ์‹œ์ผœ์ฃผ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

let descriptor = FetchDescriptor<Trip>(
  sortBy: SortDescriptor(\Trip.name),
  predicate: tripPredicate
)      

let trips = try context.fetch(descriptor)

์ด๋ ‡๊ฒŒ SortDescriptor์™€ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

context.enumerate(descriptor
                  batchSize: 10000
                  allowEscapingMutations: true) { trip in
  // operate on trip
}

์ด์™ธ์—๋„ enumerate๋ผ๋Š” ํ•จ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

batchSize๋Š” ํ•œ ๋ฒˆ์— 5000๊ฐœ์˜ ์˜ค๋ธŒ์ ํŠธ๋“ค์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ œํ•œ๋˜์–ด ์žˆ์ง€๋งŒ, ์ด๋ฅผ 10000๊ฐœ๋กœ ๋Š˜๋ฆฌ๋ฉด ๋ฉ”๋ชจ๋ฆฌ์˜ ํ• ๋‹น๋Ÿ‰์„ ๋Š˜๋ฆฌ๊ณ , I/O ์‹œ๊ฐ„์€ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋ฉ”๋ชจ๋ฆฌ ์—ฐ์‚ฐ์„ ๋””์Šคํฌ ์ฒ˜๋ฆฌ๋ณด๋‹ค ์šฐ์„ ์‹œ)

๋ฐ˜๋ฉด ์‚ฌ์ง„, ์˜ํ™”์™€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋“ค์€ batchSize๋ฅผ ์ค„์ด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ด๋ฉด์„œ I/O ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๋Š˜๋ฆด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด์ฃ . (๋””์Šคํฌ ์ฒ˜๋ฆฌ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ ์—ฐ์‚ฐ๋ณด๋‹ค ์šฐ์„ ์‹œ)

Modifying your Data

SwiftData๋Š” CRUD ์ž‘์—…๋„ ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค!

var myTrip = Trip(name: "Birthday Trip", destination: "New York")

// Create
context.insert(myTrip)

// Delete
context.delete(myTrip)

try context.save()

References

WWDC23 - Meet SwiftData

WWDC23 - Model your Schema with SwiftData

WWDC23 - Dive deeper into SwiftData