내가 iOS 개-발자라니.

옆집 스위프트: WWDC 24 SwiftData의 새로운 기능 본문

This is da(大) SWIFT

옆집 스위프트: WWDC 24 SwiftData의 새로운 기능

옆집개 2024. 6. 26. 17:13

SwiftData의 새로운 내용 상세 요약

 

SwiftData 소개

SwiftData iOS 17에서 도입된 프레임워크로, Apple 플랫폼 전반에 걸쳐 앱의 데이터를 Swift로 모델링하고 영속화하는 기능을 제공한다. Swift 언어의 현대적 기능(: 매크로)을 활용하여 빠르고 효율적이며 안전한 코드를 작성할 수 있도록 한다.

 

SwiftData의 주요 기능

1. 모델링 및 영속성(persistence):

   - SwiftData는 앱의 모델 계층을 쉽게 구축하고 앱이 실행될 때마다 데이터를 영속화한다.

   - 스키마의 모델링과 마이그레이션, 그래프 관리, CloudKit과의 동기화 기능을 제공.

 

2. @Model 매크로:

   - 모델 클래스에 @Model 매크로를 사용하여 영속성을 부여.

   - SwiftUI ‘modelContainer’ 수정자를 사용하여 모델을 앱의 뷰 계층에 통합.

 

3. 스키마 매크로:

   - #Unique 매크로: 모델 속성 조합의 고유성을 보장하여 중복을 방지하고, 충돌 시 업데이트를 수행.

   - @Attribute 매크로: 삭제 시 값을 보존하는 ‘preserveValueOnDeletion’ 속성을 추가 가능.

 

4. SwiftData 히스토리 API:

   - 모델의 삽입, 업데이트, 삭제 기록을 추적.

   - 삭제된 모델의 보존된 값을 ‘tombstone’ 값으로 히스토리에 저장.

 

5. 모델 컨테이너:

   - 기본 설정: ‘modelContainer’ 수정자를 사용하여 간단하게 컨테이너 설정 가능.

   - 사용자 정의 데이터 저장소: JSON 파일 등 사용자 정의 포맷을 지원하는 데이터 저장소 생성 가능.

   - Xcode 미리보기와 통합: 미리보기 모드에서 모델 컨테이너를 활용하여 샘플 데이터를 쉽게 사용 가능.

 

6. @Query 매크로:

   - SwiftUI 뷰에서 쿼리를 사용하여 모델 데이터를 배열로 가져옴.

   - #Predicate 매크로: 데이터를 효율적으로 필터링.

 

7. 표현식과 복합 필터링:

   - #Expression 매크로: 복잡한 평가를 표현식으로 작성하여 쿼리를 더욱 세밀하게 조정.

   - : 여행 중 아직 계획되지 않은 관광 명소를 필터링하는 복합 표현식 작성.

 

8. 성능 최적화:

   - #Index 매크로: 쿼리 성능을 향상시키기 위해 모델 속성에 인덱스 추가 가능.

   - 정렬 및 필터링에 자주 사용되는 속성에 대해 인덱스를 생성하여 대규모 데이터셋에서 성능 향상.

 

예제 앱 - Trips

1. 기본 설정:

   - 앱의 여행 아이디어를 추적하는 Trips 앱 작성.

   - @Model 매크로를 사용하여 모델 정의.

// Trip 모델에 @Model 데코레이터 적용
import Foundation
import SwiftData

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

@Model
class BucketListItem {...}

@Model
class LivingAccommodation {...}
// ModelContainer Scene Modifier를 사용한 트립 앱
import SwiftUI
import SwiftData

@main
struct TripsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView
        }
        .modelContainer(for: Trip.self)
    }
}
// @Query를 사용한 트립 앱
import SwiftUI
import SwiftData

struct ContentView: View {
    @Query
    var trips: [Trip]
    var body: some View {
        NavigationSplitView {
            List(selection: $selection) {
                ForEach(trips) { trip in
                    TripListItem(trip: trip)
                }
            }
        }
    }
}
// Trip 모델에 @Model 데코레이터 적용
import Foundation
import SwiftData

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

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

@Model
class BucketListItem {...}

@Model
class LivingAccommodation {...}

 

2. 데이터 중복 방지:

   - #Unique 매크로를 사용하여 여행의 이름, 시작일, 종료일이 고유하도록 설정.

   - 중복된 데이터가 있을 때 충돌을 방지하고 업데이트 수행.

// 중복을 방지하기 위한 고유 제약 조건 추가
import SwiftData

@Model 
class Trip {
    #Unique<Trip>([\.name, \.startDate, \.endDate])
    
    var name: String
    var destination: String
    var startDate: Date
    var endDate: Date
    
    var bucketList: [BucketListItem] = [BucketListItem]()
    var livingAccommodation: LivingAccommodation?
}
// 고유 열을 캡처하기 위해 .preserveValueOnDeletion 추가
import SwiftData

@Model 
class Trip {
    #Unique<Trip>([\.name, \.startDate, \.endDate])
    
    @Attribute(.preserveValueOnDeletion)
    var name: String
    var destination: String

    @Attribute(.preserveValueOnDeletion)
    var startDate: Date

    @Attribute(.preserveValueOnDeletion)
    var endDate: Date
    
    var bucketList: [BucketListItem] = [BucketListItem]()
    var livingAccommodation: LivingAccommodation?
}

 

3. 모델 컨테이너 사용자 정의:

   - 데이터를 디스크가 아닌 메모리에 저장.

   - autosave  undo-redo 지원 설정.

// ModelContainer Scene Modifier를 사용한 Trip 앱
// 앱에서 모델 컨테이너 사용자 커스텀
import SwiftUI
import SwiftData

@main
struct TripsApp: App {   
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Trip.self,
                        inMemory: true,
                        isAutosaveEnabled: true,
                        isUndoEnabled: true)
   }
}
// 앱에 모델 컨테이너 추가
import SwiftUI
import SwiftData

@main
struct TripsApp: App {
    var container: ModelContainer = {
        do {
            let configuration = ModelConfiguration(schema: Schema([Trip.self]), url: fileURL)
            return try ModelContainer(for: Trip.self, configurations: configuration)
        }
        catch { ... }
    }()
    
   var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(container)
   }
}
// 자신만의 커스텀 데이터 스토어 사용
import SwiftUI
import SwiftData

@main
struct TripsApp: App {
    var container: ModelContainer = {
        do {
            let configuration = JSONStoreConfiguration(schema: Schema([Trip.self]), url: jsonFileURL)
            return try ModelContainer(for: Trip.self, configurations: configuration)
        }
        catch { ... }
    }()
    
   var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(container)
   }
}

 

 

4. 샘플 데이터 프리뷰:

   - 프리뷰를 위한 샘플 데이터를 제공하여 SwiftUI 뷰 작업을 용이하게 함.

   - @Previewable 매크로를 사용하여 쿼리를 미리보기 선언에 직접 생성.

// 트레이트를 사용하여 프리뷰 데이터 생성

struct SampleData: PreviewModifier {
    static func makeSharedContext() throws -> ModelContainer {
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try ModelContainer(for: Trip.self, configurations: config)
        Trip.makeSampleTrips(in: container)
        return container
    }
    
    func body(content: Content, context: ModelContainer) -> some View {
        content.modelContainer(context)
    }
}

extension PreviewTrait where T == Preview.ViewTraits {
    @MainActor static var sampleData: Self = .modifier(SampleData())
}
// 프리뷰에서 샘플 데이터 사용

import SwiftUI
import SwiftData

struct ContentView: View {
    @Query
    var trips: [Trip]

    var body: some View {
        ...
    }
}

#Preview(traits: .sampleData) {
    ContentView()
}
// @Previewable을 사용하여 프리뷰 쿼리 생성

import SwiftUI
import SwiftData

#Preview(traits: .sampleData) {
    @Previewable @Query var trips: [Trip]
    BucketListItemView(trip: trips.first)
}

 

5. 복합 필터링 및 쿼리 최적화:

   - #Expression 매크로를 사용하여 복합 필터링을 수행.

   - #Index 매크로를 사용하여 쿼리 성능을 최적화.

// 검색 텍스트를 기반으로 Trip을 찾기 위한 Predicate 생성

let predicate = #Predicate<Trip> {
    searchText.isEmpty ? true : $0.name.localizedStandardContains(searchText)
}
// 검색 텍스트를 기반으로 Trip을 찾기 위한 복합 Predicate 생성

let predicate = #Predicate<Trip> {
    searchText.isEmpty ? true :
    $0.name.localizedStandardContains(searchText) ||
    $0.destination.localizedStandardContains(searchText)
}
// plan에 포함되지 않은 BucketListItem이 있는 Trip을 찾기 위한 Predicate 생성

let unplannedItemsExpression = #Expression<[BucketListItem], Int> { items in
    items.filter {
        !$0.isInPlan
    }.count
}

let today = Date.now
let tripsWithUnplannedItems = #Predicate<Trip>{ trip
    // The current date falls within the trip
    (trip.startDate ..< trip.endDate).contains(today) &&

    // The trip has at least one BucketListItem
    // where 'isInPlan' is false
    unplannedItemsExpression.evaluate(trip.bucketList) > 0
}
// 자주 사용되는 KeyPath 또는 KeyPath 조합에 대한 인덱스 추가
import SwiftData

@Model 
class Trip {
    #Unique<Trip>([\.name, \.startDate, \.endDate
    #Index<Trip>([\.name], [\.startDate], [\.endDate], [\.name, \.startDate, \.endDate])

    var name: String
    var destination: String
    var startDate: Date
    var endDate: Date
    
    var bucketList: [BucketListItem] = [BucketListItem
    var livingAccommodation: LivingAccommodation
}

 

결론

SwiftData는 앱의 모델 계층을 구축하고 데이터를 효율적으로 관리하는 데 강력한 도구이다. 중복 모델을 방지하기 위해 #Unique 제약을 추가하고, 쿼리를 최적화하기 위해 #Index 매크로를 사용하는 등 다양한 기능을 제공한다. SwiftData 히스토리 API를 통해 앱 모델의 변경 사항을 추적하고, 사용자 정의 데이터 저장소를 활용하여 데이터를 관리할 수 있다. 다음글은 SwiftData와 CoreData를 비교해보는 내용을 보겠다.