[iOS/WWDC23] SwiftData에 대해 알아보자

2025. 2. 20. 16:08·iOS/WWDC

SwiftData는 데이터모델링과 관리 프레임워크이다.
다른 포멧은 제외하고 코드에만 집중하며 Swift 매크로를 통해 매끄러운 API를 만든다.

Schema 구성요소

@Model

  • 스키마 정의하는 매크로
  • 값 프로퍼티가 속성으로 사용되도록 적응시킨다.
  • 기본 타입과 Struct, Enum Codable등 가능
  • @Attribute를 통해 제약조건 추가 가능
  • @Relationship으로 삭제 규칙 지정 가능
  • @Transient로 특정 프로퍼티를 포함하지 않도록 할 수 있음
import SwiftData

@Model
class Trip {
    var name: String
    var destination: String
    var startDate: Date
    var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}

@Attribute

✔️ .unique

기본 키를 지정하기 위해서는 Attribute의 .unique 옵션을 사용할 수 있다.

 

✔️ originalName

@Model
class Trip {
    @Attribute(.unique) var name: String
    var destination: String
    var start_Date: Date
    var end_Date: Date
}

위의 스키마에서 start_Date , end_Date 프로퍼티 이름의 밑줄을 없애고 싶어 변경하게 되면 이미 생성된 스키마에서 새로운 프로퍼티를 생성하게 된다.

 

이 프로퍼티를 유지하면서 이름을 변경하고싶다면 기존 이름과 프로퍼티 이름을 매핑해줄 수 있다.

@Model
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

스키마 간의 관계를 설정하고, deleteRule 을 통해 삭제 규칙을 적용할 수 있다.

  • .cascade
    • 부모 객체가 삭제되면 관련된 자식 객체도 삭제된다.
  • .nullify
    • 부모 객체가 삭제되면, 관계만 해제하고 자식 객체는 Nil이 된다.
  • .deny
    • 자식 객체가 존재하면 부모 객체는 삭제될 수 없다.
@Relationship(deleteRule: .cascade)
var bucketList: [BucketListItem]? = []

@Relationship(deleteRule.cascade)
var livingAccommodation: LivingAccommodation?

@Transient

@Transient 로 지정된 프로퍼티는 데이터를 SwiftData에 저장하지 않고, 런타임 동안에만 유지된다.

@Transient
var tripView: Int = 0

반드시 기본 값이 지정되어 있어야 한다.

ModelContainer

데이터 저장소 관리

  • 스키마와 저장 데이터간의 관계를 관리
  • 데이터가 메모리 또는 디스크에 저장되도록 저장 방식 관리
  • 버전 관리, 마이그레이션, 그래프 분리 같은 스토리지 운영 관리
let container = try ModelContainer(
    for: [ Trip.self, LivingAccommodation.self ],
    configuration: ModelConfiguration(url: URL("path"))
)
  • SwiftUI에서 .modelContainer(for: ) modifier를 사용하여 컨테이너를 쉽게 주입할 수 있다.
  • WindowGroup에 ModelContainer를 설정하면 그 안의 모든 뷰가 같은 컨테이너를 사용할 수 있다.

ModelConfiguration

  • 데이터가 저장되는 위치를 제어
    • 일시적인 데이터는 메모리에, 영구적인 데이터는 디스크에 저장
  • 특정 파일의 URL을 사용하거나, 그룹 컨테이너 권한 등 응용프로그램의 권한을 통해 URL 생성
  • 데이터를 읽기 전용 모드로 로드하여 민감한 데이터나 템플릿 데이터의 수정을 방지
  • 두 개 이상의 CloudKit 컨테이너를 사용하는 응용프로그램은 ModelConfiguration의 일부로 지정할 수 있다.
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"),
        cloudKitDatabase: .private("com.example.trips")
)

let people = ModelConfiguration(
    schema: Schema([Person.self, Address.self]),
    url: URL(filePath: "/path/to/people.store"),
    cloudKitDatabase: .private("com.example.people")
) 

let container = try? ModelContainer(
    for: fullSchema, 
    configurations: trips, people
)

fullSchema 를 통해 프로젝트 내에서 사용하기 위한 스키마를 정의한다.
ModelConfiguration 을 통해 서로 다른 저장 위치에서 스키마를 불러온다.
최종적으로 스키마와 configuration을 결합하여 ModelContainer를 구성할 수 있다.

 

위와 같이 형성한 ModelContainer는 modifier를 통해 SwiftUI 뷰에 주입할 수 있다.

var body: some Scene {
    WindowGroup {
        ContentView()
    }
    .modelContainer(container)
}

ModelContext

데이터 변경을 처리하는 컨텍스트

  • 모델의 변경사항을 추적하고, 데이터를 저장하거나 삭제하는 기능을 제공한다.
  • 컨테이너가 설정되면 ModelContext로 데이터를 가져와 저장할 준비를 할 수 있다.
  • rollback과 reset기능을 지원하여 캐싱된 상태를 지울 수 있다.
  • 자동 저장

Context 주입

View나 Scene에서 .modelContainer 를 통해 주입하면, 컨테이너의 mainContext 에 modelContext 키를 바인딩 한다.

  • MainContext는 특수한 MainActor 정렬 모델 컨텍스트로, Scene이나 View에서 ModelObject와 함께 작동한다.
struct ContextView: View {
    @Environment(\.modelContext) private var context
}

Context 접근

@Environment(\.modelContext) 를 사용하여 SwiftUI View 코드에서 모델의 쿼리에 쓰인 컨텍스트에 쉽게 접근할 수 있다.

  • @Environment(\.modelContext) 는 기존 데이터의 변경사항을 추적할 수 있다.
struct ContentView: View {
    @Query var trips: [Trip]
    @Environment(\.modelContext) private var context
    var body: some View {
        ...
        Button {
            modelContext.delete(trip)
        } label: {
            Label("Delete", systemImage: "trash")
        }
    }
}

모델에 변경사항이 생기면 스냅샷으로 ModelContext에 기록된다. 계속해서 변경사항을 추적하다가 context.save() 를 호출하면 멈춘다.

→ 즉, 데이터를 삭제하더라도 context.save() 를 하기 전 까지는 ModelContext에 남아있게 된다.

@Query

@State와 비슷하게 동작하여, 모델에서 일어나는 모든 변화를 뷰에 업데이트한다.

@Query를 사용하여 정렬, 필터와 같은 간단한 구문을 처리한 후의 데이터를 받아올 수 있다.

@Query(sort: \.created) private var cards: [Card]

Undo/Redo

.modelContainer() 는 isUndoEnable 이라는 매개변수가 있다.

.modelContainer(for: Trip.self, isUndoEnabled: true)

true 값을 주면 MainContext에 undoManager를 바인딩하여, 추가 코드 없이 변경사항을 취소 또는 복귀를 수행할 수 있다.

자동 저장

자동저장 기능이 활성화되면 ModelContext는 시스템 이벤트(백그라운드 전환 또는 특정 주기) 발생 시 저장된다. 자동저장은 기본으로 활성화되어있어, 불필요 시 비활성화 할 수 있다.

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

Model at scale

백그라운드에서 데이터를 다루거나 원격 서버, 다른 영구 메커니즘과의 동기화, 배치 프로세스는 모두 모델 객체를 필요로한다.

이러한 작업에서 필요한 객체 집합을 가져올 때 ModelContext의 .fetch() 메서드를 통해 가져온다.

FetchDescriptor

FetchDescriptor를 사용하여 복잡한 쿼리를 만들 수 있다.

#Predicate 매크로를 스키마와 결합하여 컴파일러가 검증한 쿼리를 사용할 수 있다.

파라미터로 offset, limt, faulting, perfetching 옵션을 사용할 수 있다.

 

✔️ #Predicate

특정 조건을 기반으로 데이터를 필터링 할 때 사용할 수 있다.

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)

 

✔️ SortDescriptor

FetchDescriptor 와 함께 사용하여 데이터를 가져올 때 정렬을 수행한 후 값을 가져오도록 할 수 있다.

let descriptor = FetchDescriptor<Trip>(
    sortBy: SortDescriptor(\\Trip.name),
    perdicate: tripPredicate
    )
let trips = try context.fetch(descriptor)

Tuning Option

ModelContext의 enumerate함수에서 옵션들이 결합된다.

context.enumerate(FetchDescriptor<Trip>()) { trip in

}

 

✔️ batchSize 조절

enumerate함수를 통해 배치사이즈 크기를 조절할 수 있다.

context.enumerate(
    descriptor,
    batchSize: 1000) {}

배치 크기를 줄이면 메모리를 적게 사용하게 되지만 I/O가 증가한다.

 

✔️ mutation guard

데이터를 순회하는 동안 데이터가 변경됨을 감지하는 mutation guard를 내부적으로 포함하고 있다.

context.enumerate(descriptor) { trip in
    trip.name = "새로운 이름" // ❌ 예외 발생 가능!
}

기본적으로 ModelContext가 변형되는 것을 감지하여 예외를 발생시켜 데이터의 일관성으로 보장하려는 목적을 가지고 있다.

 

하지만, 의도적으로 일부 데이터를 변경하려고 할 수 있다. 이러한 경우 allowEscapingMutations 매개변수를 true로 설정하여 데이터의 변경을 허용할 수 있다.

context.enumerate(
    descriptor,
    batchSize: 500,
    allowEscapingMutations: true
) { trip in
    // Remind me to make reservations for trip
}

 

 

참고

https://developer.apple.com/videos/play/wwdc2023/10187

 

Meet SwiftData - WWDC23 - Videos - Apple Developer

SwiftData is a powerful and expressive persistence framework built for Swift. We'll show you how you can model your data directly from...

developer.apple.com

 

https://developer.apple.com/videos/play/wwdc2023/10195

 

Model your schema with SwiftData - WWDC23 - Videos - Apple Developer

Learn how to use schema macros and migration plans with SwiftData to build more complex features for your app. We'll show you how to...

developer.apple.com

 

https://developer.apple.com/videos/play/wwdc2023/10196

 

Dive deeper into SwiftData - WWDC23 - Videos - Apple Developer

Learn how you can harness the power of SwiftData in your app. Find out how ModelContext and ModelContainer work together to persist your...

developer.apple.com

 

저작자표시 (새창열림)

'iOS > WWDC' 카테고리의 다른 글

[iOS/WWDC23] SwiftData 마이그레이션  (3) 2025.02.25
[iOS/WWDC23] viewIsAppearing() 알아보기  (1) 2024.03.24
'iOS/WWDC' 카테고리의 다른 글
  • [iOS/WWDC23] SwiftData 마이그레이션
  • [iOS/WWDC23] viewIsAppearing() 알아보기
김졀니
김졀니
🍎 iOS 개발
  • 김졀니
    졀니의 개발 공부✨
    김졀니
  • 전체
    오늘
    어제
    • 분류 전체보기
      • iOS
        • Swift
        • UIKit&SwiftUI
        • RxSwift&Combine
        • WWDC
      • Study
        • 🚨 TroubleShooting
        • 🌱 SeSAC
  • 블로그 메뉴

    • 홈
    • Github
  • 인기 글

  • 최근 글

  • 태그

    mapkit
    의존성 주입
    swift concurrency
    ReactorKit
    observable
    ios
    displayPriority
    swiftdata
    pointfree
    wwdc23
    layoutIfNeeded
    위치 권한
    traits
    Realm
    이미지 캐싱
    동시성프로그래밍
    Sendable
    clipstobounds
    인앱리뷰
    actor
    FileManager
    @PropertyWrapper
    RxSwift
    Drawing Cycle
    concurrency
    kingfisher header
    CLLocation
    mainactor
    OperationQueue
    Swift
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
김졀니
[iOS/WWDC23] SwiftData에 대해 알아보자
상단으로

티스토리툴바