Section 9/181 menit

9. Migrasi Schema

9. Migrasi Schema

Setiap kali schema berubah (tambah properti, ubah nama, hapus model), SwiftData harus diberitahu agar bisa memigrasikan data lama.

Lightweight Migration (Otomatis)

Untuk perubahan sederhana — menambah properti opsional atau dengan default value — SwiftData dapat memigrasi secara otomatis tanpa konfigurasi tambahan:

swift
// v1: model awal
@Model final class Item {
    var name: String
    init(name: String) { self.name = name }
}

// v2: tambah properti dengan default — lightweight migration otomatis
@Model final class Item {
    var name: String
    var createdAt: Date = Date()   // default value → safe untuk migrate
    var notes: String?              // optional → safe untuk migrate
    init(name: String) { self.name = name }
}

Custom Migration dengan VersionedSchema

Untuk perubahan yang lebih kompleks (rename properti, ubah tipe, hapus dan re-create relasi):

swift
// MARK: - Schema Versions

enum AppSchema: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)
    static var models: [any PersistentModel.Type] { [AppSchema.Task.self] }

    @Model
    final class Task {
        var title: String
        var done: Bool  // nama lama

        init(title: String) {
            self.title = title
            self.done = false
        }
    }
}

enum AppSchemaV2: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)
    static var models: [any PersistentModel.Type] { [AppSchemaV2.Task.self] }

    @Model
    final class Task {
        var title: String
        var isCompleted: Bool  // nama baru — perlu custom migration
        var priority: Int

        init(title: String) {
            self.title = title
            self.isCompleted = false
            self.priority = 0
        }
    }
}

// MARK: - Migration Plan

enum AppMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [AppSchema.self, AppSchemaV2.self]
    }

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

    static let migrateV1toV2 = MigrationStage.custom(
        fromVersion: AppSchema.self,
        toVersion: AppSchemaV2.self,
        willMigrate: nil,
        didMigrate: { context in
            // Setelah migrasi struktural selesai, migrasikan data
            let descriptor = FetchDescriptor<AppSchemaV2.Task>()
            let tasks = try context.fetch(descriptor)
            // isCompleted sudah di-set dari 'done' oleh Core Data migration engine
            // karena kita register mapping di originalName
            try context.save()
        }
    )
}

// MARK: - Penggunaan di App

@main
struct MyApp: App {
    let container: ModelContainer

    init() {
        do {
            container = try ModelContainer(
                for: AppSchemaV2.Task.self,
                migrationPlan: AppMigrationPlan.self
            )
        } catch {
            fatalError("Failed to create container: \(error)")
        }
    }

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