• 데이터 모델링과 관리를 위한 프레임워크

    • SwiftUI처럼 모든 정보는 파일에 담기고 외부파일 참조는 없다.
    • Swift의 매크로를 통해서 막힘없는 API 경험을 준다.
    • SwiftUI와 자연스럽게 통합되고 CloudKit과 Widget등과 함께 잘 동작한다.
  • Using the Model Macro

    • @Model 매크로를 타입에 붙여서 정의

      • 스키마를 코드를 가지고 정의하는 것

      • 기본값은 다 있지만, 필요시 추가 메타데이터를 줄 수 있다.

        import SwiftData
        
        // class의 stored property들이 persistent property로 바뀐다.
        @Model
        class Trip {
            var name: String
            var destination: String
            var endDate: Date
            var startDate: Date
         
            var bucketList: [BucketListItem]? = []
            var livingAccommodation: LivingAccommodation?
        }
        
    • Attribute

      • 프로퍼티를 기반으로 자동 추론됨
      • 기본 값타입 뿐 아니라 복잡한 값타입(struct, enum, Codable, Collection) 지원
    • Relationship

      • 참조 타입간의 관계를 통해서 자동 추론
        • 다른 모델 타입
        • 모델들의 콜렉션 타입
    • 기본적으로는 모든 stored property들을 변경하는데, 어떻게 바뀌는지도 매크로를 통해서 조정할 수 있다.

      • @Attribute: 독특한 제약을 추가할 수 있다.

      • @RelationShip: 관계를 역전시키거나, 전파를 막을 수도 있다.

      • @Transient: 모델에서 제외한다

        @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
      • Configuration을 통해서 커스텀 가능
      • 스키마 마이그레이션 옵션도 제공
    • 초기화

      • 기본적으로는 원하는 타입 목록만 명시하면 됨

      • 커스텀 컨테이너, CloudKit, Group Container 혹은 마이그레이션 옵션을 주고 싶으면 Configuration 제공

        // Initialize with only a schema
        let container = try ModelContainer([Trip.self, LivingAccommodation.self])
        
        // Initialize with configurations
        let container = try ModelContainer(
            for: [Trip.self, LivingAccommodation.self],
            configurations: ModelConfiguration(url: URL("path"))
        )
        
      • SwiftUI의 view와 scene modifier로도 컨테이너를 설정할 수 있다.

        import SwiftUI
        
        @main
        struct TripsApp: App {
            var body: some Scene {
                WindowGroup {
                    ContentView()
                }
                .modelContainer(
                    for: [Trip.self, LivingAccommodation.self]
                )
            }
        }
        
    • modelContext가 모든 변경을 감지하고 그 변경에 대해서 수행할 수 있는 많은 액션을 제공한다.

      • 업데이트 추적
      • 데이터 가져오기
      • 변경사항 저장
      • 변경 취소
    • SwiftUI에서는 이 context를 Environment를 통해서 가져온다.

      import SwiftUI
      
      struct ContextView : View {
          @Environment(\\.modelContext) private var context
      }
      
    • 뷰 바깥에서는 mainContext를 가져오거나, 새로운 Context를 만들 수 있다.

      import SwiftData
      
      let mainContext = container.mainContext
      let newContext = ModelContext(container)
      
    • Predicate(Foundation)

      • 이제 네이티브 Swift 타입이다.

      • 타입 체크 기능을 지원한다.

      • 텍스트를 직접 파싱하지 않고, #Predicate 매크로로 만든다.

      • 자동완성도 지원한다.

        let today = Date()
        let tripPredicate = #Predicate<Trip> { 
            $0.destination == "New York" &&
            $0.name.contains("birthday") &&
            $0.startDate > today
        }
        
    • FetchDescriptor(SwiftDate)

      • 역시 네이티브 Swift 타입으로 새로 추가됨.

        let descriptor = FetchDescriptor<Trip>(predicate: tripPredicate)
        
        let trips = try context.fetch(descriptor)
        
      • 여러 옵션 지원

        • prefetch할 relationship
        • 결과값 제한
        • 변경 사항 저장에서 제외하기
        • 그 외
    • SortDescriptor(Foundation)

      • iOS 15부터 있었다.

      • 이번에 Swift 네이티브 Comparable과 KeyPath를 지원하게 되었다.(기존은 NSObject 기반)

        let descriptor = FetchDescriptor<Trip>(
            sortBy: SortDescriptor(\\Trip.name),
            predicate: tripPredicate
        )
        
        let trips = try context.fetch(descriptor)
        
    • ModelContext 다루기

      • 일반적인 Swift 클래스로 만들어서, context를 통해서 다루면 된다.

        var myTrip = Trip(name: "Birthday Trip", destination: "New York")
        
        // Insert a new trip
        context.insert(myTrip)
        
        // Delete an existing trip
        context.delete(myTrip)
        
        // Manually save changes to the context
        try context.save()
        
      • @Model 매크로가 setter를 변경을 추적하고 감지할 수 있도록 바꿔준다.

        • 업데이트는 ModelContext에 의해서 자동으로 이뤄진다.
  • Use SwiftData in SwiftUI

    • Scene/View Modifier를 사용한다.

      • .modelContainer를 통해서 설정하고, SwiftUI Environment를 통해서 전파한다.
    • Query 매크로를 통해서 fetch한다.

      • Published는 불필요하고, fetch가 일어날 때마다 자동으로 refresh한다.
      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 
                       // ...
                   }
                }
             }
          }
      }