꾸준한 기록

[WWDC23] Meet SwiftData 본문

Swift

[WWDC23] Meet SwiftData

Doo혀니 2023. 11. 2. 23:18

Meet SwiftData

Meet SwiftData - WWDC23 - Videos - Apple Developer

  • Local DB 관리를 위한 데이터 모델링에 사용
  • SwiftMacro를 사용

@Model 매크로


  • Swift 코드를 가지고 모델의 스키마를 정의함
  • class로 선언해야 함
import SwiftData

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

    var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}
  • Attribute
    • stored property 중에서 value type인 것들이 모델의 attribute가 됨
    • collection 타입도 모델의 attribute가 될 수 있음
  • Relationship
    • reference type을 가지고 모델간의 관계(Relation)을 정의함
    • property에 다른 model, model의 collection 이 존재하면, 해당 model과 relation을 정의
  • Metadata
    • 메타데이터 (@Attribute(.unique), @Relationship(.cascade))를 이용하여 유일성(Unique)제약이나, delete제약을 설정할 수 잇음
    • @Relationship - delete 전파 제약 정의
    • @Transient - 모델에서 특정 프로퍼티 제외
import SwiftData

@Model
class Trip {
    @Attribute(.unique) var name: String 
    var destination: String 
    var endDate: Date 
    var startDate: Date

    @Relationship(.cascade) var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}

Working with your data


ModelContainer

  • 데이터가 저장되는 영구저장소(persistent backend)를 나타냄
  • 데이터 마이그레이션 옵션 등을 커스터마이징 가능
  • Model Container 생성 코드
  1. 직접 생성
    • for: ModelContainer에 저장할 모델 타입
    • configuration: ModelConfiguration 을 통해 저장소 url, Cloud kit, group container identifier, 데이터 마이그레이션 옵션을 지정함
  2. let container = try ModelContainer(for: [Trip.self, LivingAccommodation.self], configurations: ModelConfiguration(url: URL("path")))
  3. SwiftUI의 modifier 이용
.modelContainer(for: [Trip.self, LivingAccommodation.self])

ModelContext

  • ModelContainer가 생성되면, ModelContext를 이용하여 데이터 저장, fetch 수행함
  • SwiftUI의 view modifier, scene modeifier와 함께 사용하여 컨테이터(ModelContainer)를 설정하고 뷰의 환경에 컨테이너를 배치할 수 있습니다.
import SwiftData 
import SwiftUI

@main
struct TripsApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
        } 
        .modelContainer(for:
            [Trip.self, 
            LivingAccommodation.self])
        )
    }
}
  • 모델의 변화를 추적하며 다음과 같은 기능 제공
    1. 모델 데이터 변경 추적
    2. 모델 데이터 fetch
    3. 모델 데이터 save
    4. 모델 데이터 변경 undo
  • 일반적으로 SwiftUI에서는 Model Context를 view environment로부터 가지고 온다.
import SwiftData 
import SwiftUI

struct ContextView : View {
    @Environment(\.modelContext) private var context
}
  • view 계층 밖에서는 main actor 영역에서 공유되는 context를 사용가능
// view 계층 박
// or outside the view hierarchy, a shared main actor bound context,
let context = container.mainContext
  • model container를 가지고 새로운 model context 생성 가능
// or instantiate new contexts for a given model container.
let context = ModelContext(container)

Fetching Data

  • 새로운 타입: Predicate, FetchDescriptor 등장
  • Predicate
    • NSPredicate의 대체제
    • Swift Code 이용하여 query 조건 작성할 수 있도록 지원함
    • Swift Macro를 통해 컴파일 타임 타입체크를 지원함
    • key-path 자동 완성 지원함
  • FetchDescriptor
    • FetchDescriptor를 사용해서 context에게 데이터 fetch 요청
let descriptor = FetchDescriptor<Trip>(predicate: tripPredicate) 
let trips = try context.fetch (descriptor)

 

  • 관계(Relationship)이 설정된 모델을 prefetch할 수 있음
  • result limit 설정 가능
  • 저장되지 않은 변경은 제외하고 fetch 가능
// I can specify all the trips whose destination is New York. 
let tripPredicate = #Predicate<Trip> { 
	S0.destination == "New York" 
}
    
// I can narrow our query down to just trips about birthdays 
let tripPredicate = #Predicate<Trip> { 
	S0.destination == "New York" && $0.name.contains("birthday") 
} 

// and I can specify we're only interested in trips planned for the future, as opposed to any of our past adventures. 
let today = Date() 
let tripPredicate = #Predicate<Trip> { 
	$0.destination == "New York" && $0.name.contains("birthday") && $0.startDate > today 
}

SortDescriptor

  • 모든 comparable 타입 사용 가능
  • Swift native keypaths 사용 가능
let descriptor = FetchDescriptor<Trip>(
    sortBy: SortDescriptor(\Trip.name),
    predicate: tripPredicate
)
let trips = try context.fetch(descriptor)

데이터 수정

  • 삽입, 삭제, 저장, 수정 가능
  • 데이터 삭제 방법: context에게 삭제할 데이터 알림 → 영구저장소에 데이터 삭제를 반영 요청
var myTrip = Trip(name: "Birthday Trip", destination: "New York")

// myTrip 삽입
context.insert(myTrip)

// myTrip 제거 
context.delete(myTrip)

// context에 변경 사항을 영구저장소에 반영 요청
try context.save()

Use SwiftData in SwiftUI


  • SwiftUI에서 SwiftData를 사용하기 위한 준비 작업이 간단함
  • 자동으로 데이터 fetch하여 뷰에 반영함

SwiftData와 관련한 view modifier 지원

  • .modelContainer()를 사용하여 ModelContainer 생성
  • 이 ModelContainer에서 발생한 변경사항은 SwiftUI environment에 전파됨
  • @Query 를 이용하여 데이터 바로 fetch 가능
import SwiftData import SwiftUI

struct ContentView: View { 
    @Query(sort: .startDate, order: .reverse) var trips: [Trip] 
    @Environment(.modelContext) var modelContext

    var body: some View { 
        NavigationStack() { 
            List { ForEach(trips) { trip in 
                // ... 
                } 
            } 
        } 
    } 
}

Observing changes

  • @Published 사용하지 않아도,SwiftData에 데이터 변경사항이 발생하면 SwiftUI가 뷰에 자동으로 반영