Di chuyển từ Core Data sang SwiftData: Hướng dẫn từng bước 2026

Hướng dẫn đầy đủ để di chuyển ứng dụng iOS từ Core Data sang SwiftData với các ví dụ thực tế, chiến lược cùng tồn tại và những thực hành tốt nhất.

Hướng dẫn di chuyển từ Core Data sang SwiftData cho lập trình viên iOS

SwiftData đại diện cho tương lai của lưu trữ dữ liệu trên các nền tảng Apple. Được giới thiệu tại WWDC 2023, framework này cung cấp cú pháp Swift gốc và tích hợp mượt mà với SwiftUI. Đối với các ứng dụng Core Data hiện có, việc di chuyển là một bước chiến lược hướng tới mã nguồn hiện đại và dễ bảo trì hơn.

Bài viết này đề cập

Hướng dẫn này trình bày chi tiết toàn bộ quy trình di chuyển từ Core Data sang SwiftData: đánh giá tính tương thích, chuyển đổi mô hình, chiến lược di chuyển dữ liệu và các mẫu cùng tồn tại để chuyển đổi dần dần.

Đánh giá Tính khả thi của Việc Di chuyển

Trước khi bắt đầu di chuyển, một đánh giá kỹ lưỡng cho phép xác định các trở ngại tiềm ẩn. Core Data và SwiftData chia sẻ cùng một engine lưu trữ SQLite, làm cho dữ liệu hoàn toàn tương thích.

MigrationAssessment.swiftswift
// Migration assessment checklist

/*
 FEATURES SUPPORTED BY SWIFTDATA:
 ✅ Simple models with basic properties
 ✅ One-to-one and one-to-many relationships
 ✅ Optional properties and default values
 ✅ Transformable attributes (via Codable)
 ✅ CloudKit synchronization (basic)
 ✅ Automatic lightweight migrations
 ✅ Class inheritance (iOS 26+)

 FEATURES REQUIRING ATTENTION:
 ⚠️ NSFetchedResultsController → @Query + manual observation
 ⚠️ NSCompoundPredicate → #Predicate with combined logic
 ⚠️ Dynamic predicates → Workarounds required

 UNSUPPORTED FEATURES:
 ❌ Advanced CloudKit Sharing
 ❌ Derived attributes
 ❌ Fetched properties
*/

// Example of typical Core Data model to migrate
import CoreData

// Existing Core Data entity
class CDTask: NSManagedObject {
    @NSManaged var id: UUID
    @NSManaged var title: String
    @NSManaged var isCompleted: Bool
    @NSManaged var createdAt: Date
    @NSManaged var priority: Int16
    @NSManaged var category: CDCategory?
}

class CDCategory: NSManagedObject {
    @NSManaged var id: UUID
    @NSManaged var name: String
    @NSManaged var color: String
    @NSManaged var tasks: NSSet?
}

Tính tương thích ở cấp độ dữ liệu có nghĩa là người dùng giữ lại thông tin hiện có sau khi di chuyển. Không có việc mất dữ liệu xảy ra khi quy trình được thực hiện đúng cách.

Chuyển đổi Mô hình Core Data sang SwiftData

Bước cụ thể đầu tiên là chuyển đổi các thực thể Core Data thành các lớp SwiftData. Xcode cung cấp công cụ tự động, nhưng việc hiểu quy trình thủ công vẫn rất quan trọng.

TaskModel.swiftswift
import SwiftData

// SwiftData equivalent of CDTask
@Model
final class Task {
    // Properties with default values
    var id: UUID = UUID()
    var title: String = ""
    var isCompleted: Bool = false
    var createdAt: Date = Date()
    var priority: Int = 0

    // Optional relationship to Category
    var category: Category?

    // Explicit initializer recommended
    init(
        id: UUID = UUID(),
        title: String,
        isCompleted: Bool = false,
        createdAt: Date = Date(),
        priority: Int = 0,
        category: Category? = nil
    ) {
        self.id = id
        self.title = title
        self.isCompleted = isCompleted
        self.createdAt = createdAt
        self.priority = priority
        self.category = category
    }
}

Những khác biệt chính so với Core Data bao gồm việc sử dụng macro @Model thay cho NSManagedObject, cùng với các kiểu Swift gốc thay vì các kiểu Objective-C.

CategoryModel.swiftswift
import SwiftData

@Model
final class Category {
    var id: UUID = UUID()
    var name: String = ""
    var color: String = "blue"

    // Inverse relationship with delete rule
    @Relationship(deleteRule: .cascade, inverse: \Task.category)
    var tasks: [Task] = []

    init(id: UUID = UUID(), name: String, color: String = "blue") {
        self.id = id
        self.name = name
        self.color = color
    }
}
Ánh xạ kiểu

Các kiểu Core Data chuyển đổi trực tiếp: Int16 thành Int, NSSet thành [Model], và Date vẫn là Date. Các thuộc tính Transformable yêu cầu áp dụng Codable.

Cấu hình ModelContainer

ModelContainer của SwiftData thay thế NSPersistentContainer của Core Data. Cấu hình xác định nơi và cách dữ liệu được lưu trữ.

ModelContainerSetup.swiftswift
import SwiftData
import SwiftUI

@main
struct TaskManagerApp: App {
    // SwiftData container configuration
    var sharedModelContainer: ModelContainer = {
        // Schema including all models
        let schema = Schema([
            Task.self,
            Category.self
        ])

        // Configuration with storage options
        let modelConfiguration = ModelConfiguration(
            schema: schema,
            isStoredInMemoryOnly: false,
            // Use the same store as Core Data
            url: URL.applicationSupportDirectory
                .appending(path: "TaskManager.sqlite")
        )

        do {
            return try ModelContainer(
                for: schema,
                configurations: [modelConfiguration]
            )
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

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

Điểm quan trọng nằm ở URL của store: sử dụng cùng tệp SQLite với Core Data cho phép SwiftData đọc dữ liệu hiện có.

Chiến lược Cùng tồn tại giữa Core Data và SwiftData

Đối với các ứng dụng phức tạp, việc di chuyển dần dần thông qua sự cùng tồn tại của cả hai framework là cách tiếp cận an toàn nhất. Cả hai stack đều có thể truy cập cùng một tệp SQLite.

CoexistenceSetup.swiftswift
import CoreData
import SwiftData

// Configuration for coexistence
class PersistenceController {
    static let shared = PersistenceController()

    // Shared store between Core Data and SwiftData
    private let storeURL: URL = {
        let appSupport = FileManager.default
            .urls(for: .applicationSupportDirectory, in: .userDomainMask)
            .first!
        return appSupport.appending(path: "TaskManager.sqlite")
    }()

    // MARK: - Core Data Stack (existing)

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "TaskManager")

        // Configure to use shared store
        let description = NSPersistentStoreDescription(url: storeURL)
        description.setOption(
            true as NSNumber,
            forKey: NSPersistentHistoryTrackingKey
        )
        container.persistentStoreDescriptions = [description]

        container.loadPersistentStores { _, error in
            if let error = error as NSError? {
                fatalError("Core Data error: \(error)")
            }
        }

        return container
    }()

    // MARK: - SwiftData Stack (new)

    lazy var swiftDataContainer: ModelContainer = {
        let schema = Schema([Task.self, Category.self])

        let config = ModelConfiguration(
            schema: schema,
            url: storeURL,
            // Disable automatic migrations in coexistence
            allowsSave: true
        )

        do {
            return try ModelContainer(for: schema, configurations: [config])
        } catch {
            fatalError("SwiftData error: \(error)")
        }
    }()
}
Đồng bộ hóa các thay đổi

Ở chế độ cùng tồn tại, các thay đổi do một framework thực hiện không hiển thị ngay lập tức cho framework kia. Có thể cần phải tải lại tường minh hoặc khởi động lại ứng dụng.

Di chuyển Truy vấn: Từ NSFetchRequest sang @Query

Khác biệt đáng chú ý nhất liên quan đến cách dữ liệu được truy xuất. SwiftUI sử dụng property wrapper @Query để thay thế @FetchRequest.

QueryMigration.swiftswift
import SwiftUI
import SwiftData

// ❌ Old pattern with Core Data
struct OldTaskListView: View {
    @FetchRequest(
        sortDescriptors: [
            NSSortDescriptor(keyPath: \CDTask.createdAt, ascending: false)
        ],
        predicate: NSPredicate(format: "isCompleted == NO")
    )
    private var tasks: FetchedResults<CDTask>

    var body: some View {
        List(tasks) { task in
            Text(task.title)
        }
    }
}

// ✅ New pattern with SwiftData
struct NewTaskListView: View {
    // @Query with built-in sorting and filtering
    @Query(
        filter: #Predicate<Task> { !$0.isCompleted },
        sort: \Task.createdAt,
        order: .reverse
    )
    private var tasks: [Task]

    var body: some View {
        List(tasks) { task in
            TaskRowView(task: task)
        }
    }
}

struct TaskRowView: View {
    let task: Task

    var body: some View {
        HStack {
            // Priority indicator
            Circle()
                .fill(priorityColor)
                .frame(width: 8, height: 8)

            VStack(alignment: .leading) {
                Text(task.title)
                    .font(.headline)

                if let category = task.category {
                    Text(category.name)
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
            }

            Spacer()

            // Date badge
            Text(task.createdAt, style: .date)
                .font(.caption2)
                .foregroundStyle(.tertiary)
        }
    }

    private var priorityColor: Color {
        switch task.priority {
        case 3: return .red
        case 2: return .orange
        case 1: return .yellow
        default: return .gray
        }
    }
}

Sẵn sàng chinh phục phỏng vấn iOS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Xử lý Các Predicate Động

Một thách thức chính với SwiftData liên quan đến các predicate động. Khác với Core Data nơi predicate có thể được sửa đổi trực tiếp, @Query yêu cầu các phương pháp thay thế.

DynamicPredicates.swiftswift
import SwiftUI
import SwiftData

// Solution 1: Use @Query with custom init
struct FilteredTasksView: View {
    @Query private var tasks: [Task]

    // Create view with specific filter
    init(showCompleted: Bool, categoryId: UUID?) {
        // Build predicate based on parameters
        var predicates: [Predicate<Task>] = []

        if !showCompleted {
            predicates.append(#Predicate { !$0.isCompleted })
        }

        if let categoryId {
            predicates.append(#Predicate { task in
                task.category?.id == categoryId
            })
        }

        // Combine predicates
        let combinedPredicate: Predicate<Task>?
        if predicates.isEmpty {
            combinedPredicate = nil
        } else if predicates.count == 1 {
            combinedPredicate = predicates[0]
        } else {
            // Manually combine for AND logic
            combinedPredicate = #Predicate<Task> { task in
                !task.isCompleted && task.category?.id == categoryId
            }
        }

        _tasks = Query(
            filter: combinedPredicate,
            sort: \Task.createdAt,
            order: .reverse
        )
    }

    var body: some View {
        List(tasks) { task in
            TaskRowView(task: task)
        }
    }
}

// Solution 2: View-side filtering with all results
struct SmartTaskListView: View {
    // Fetch all tasks
    @Query(sort: \Task.createdAt, order: .reverse)
    private var allTasks: [Task]

    // Filter state
    @State private var searchText = ""
    @State private var showCompleted = false
    @State private var selectedCategory: Category?

    // Computed filtering
    private var filteredTasks: [Task] {
        allTasks.filter { task in
            // Text filter
            let matchesSearch = searchText.isEmpty ||
                task.title.localizedCaseInsensitiveContains(searchText)

            // Status filter
            let matchesStatus = showCompleted || !task.isCompleted

            // Category filter
            let matchesCategory = selectedCategory == nil ||
                task.category?.id == selectedCategory?.id

            return matchesSearch && matchesStatus && matchesCategory
        }
    }

    var body: some View {
        NavigationStack {
            List(filteredTasks) { task in
                TaskRowView(task: task)
            }
            .searchable(text: $searchText)
            .toolbar {
                FilterMenu(
                    showCompleted: $showCompleted,
                    selectedCategory: $selectedCategory
                )
            }
        }
    }
}

Di chuyển Schema có Phiên bản

Khi mô hình dữ liệu phát triển, SwiftData sử dụng VersionedSchema để quản lý các quá trình di chuyển phức tạp.

VersionedSchemas.swiftswift
import SwiftData

// Version 1: Initial schema
enum TaskSchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [Task.self, Category.self]
    }

    @Model
    final class Task {
        var id: UUID = UUID()
        var title: String = ""
        var isCompleted: Bool = false
        var createdAt: Date = Date()
        var category: Category?

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

    @Model
    final class Category {
        var id: UUID = UUID()
        var name: String = ""

        @Relationship(deleteRule: .cascade, inverse: \Task.category)
        var tasks: [Task] = []

        init(name: String) {
            self.name = name
        }
    }
}

// Version 2: Added priority and notes fields
enum TaskSchemaV2: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)

    static var models: [any PersistentModel.Type] {
        [Task.self, Category.self]
    }

    @Model
    final class Task {
        var id: UUID = UUID()
        var title: String = ""
        var isCompleted: Bool = false
        var createdAt: Date = Date()
        // New properties with default values
        var priority: Int = 0
        var notes: String = ""
        var category: Category?

        init(title: String, priority: Int = 0) {
            self.title = title
            self.priority = priority
        }
    }

    @Model
    final class Category {
        var id: UUID = UUID()
        var name: String = ""
        // New property
        var color: String = "blue"

        @Relationship(deleteRule: .cascade, inverse: \Task.category)
        var tasks: [Task] = []

        init(name: String, color: String = "blue") {
            self.name = name
            self.color = color
        }
    }
}

Kế hoạch di chuyển xác định thứ tự các phiên bản và các quá trình di chuyển tùy chỉnh có thể cần thiết.

MigrationPlan.swiftswift
import SwiftData

enum TaskMigrationPlan: SchemaMigrationPlan {
    // Chronological order of schemas
    static var schemas: [any VersionedSchema.Type] {
        [TaskSchemaV1.self, TaskSchemaV2.self]
    }

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

    // V1 → V2 migration: lightweight (properties with defaults)
    static let migrateV1toV2 = MigrationStage.lightweight(
        fromVersion: TaskSchemaV1.self,
        toVersion: TaskSchemaV2.self
    )
}

// Container configuration with migration
@main
struct TaskManagerApp: App {
    var sharedModelContainer: ModelContainer = {
        do {
            return try ModelContainer(
                for: TaskSchemaV2.Task.self, TaskSchemaV2.Category.self,
                migrationPlan: TaskMigrationPlan.self
            )
        } catch {
            fatalError("Migration failed: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
    }
}
Di chuyển lightweight và tùy chỉnh

SwiftData tự động xử lý các quá trình di chuyển lightweight (thêm thuộc tính với giá trị mặc định, đổi tên, xóa). Các quá trình di chuyển phức tạp yêu cầu chuyển đổi dữ liệu sử dụng MigrationStage.custom.

Thay thế NSFetchedResultsController

Đối với các danh sách có section hoặc giám sát chi tiết các thay đổi, @Query kết hợp với trích xuất dữ liệu thay thế NSFetchedResultsController.

SectionedResults.swiftswift
import SwiftUI
import SwiftData

struct SectionedTaskListView: View {
    @Query(sort: \Task.createdAt, order: .reverse)
    private var tasks: [Task]

    // Grouping by category
    private var tasksByCategory: [(Category?, [Task])] {
        Dictionary(grouping: tasks) { $0.category }
            .map { ($0.key, $0.value) }
            .sorted { first, second in
                // Tasks without category last
                guard let firstName = first.0?.name else { return false }
                guard let secondName = second.0?.name else { return true }
                return firstName < secondName
            }
    }

    var body: some View {
        List {
            ForEach(tasksByCategory, id: \.0?.id) { category, categoryTasks in
                Section(header: SectionHeader(category: category)) {
                    ForEach(categoryTasks) { task in
                        TaskRowView(task: task)
                    }
                }
            }
        }
    }
}

struct SectionHeader: View {
    let category: Category?

    var body: some View {
        HStack {
            if let category {
                Circle()
                    .fill(Color(category.color))
                    .frame(width: 12, height: 12)
                Text(category.name)
            } else {
                Text("Uncategorized")
                    .foregroundStyle(.secondary)
            }
        }
    }
}

// Alternative: Grouping by date
struct DateGroupedTasksView: View {
    @Query(sort: \Task.createdAt, order: .reverse)
    private var tasks: [Task]

    private var tasksByDate: [(Date, [Task])] {
        let calendar = Calendar.current

        let grouped = Dictionary(grouping: tasks) { task in
            calendar.startOfDay(for: task.createdAt)
        }

        return grouped
            .map { ($0.key, $0.value) }
            .sorted { $0.0 > $1.0 }
    }

    var body: some View {
        List {
            ForEach(tasksByDate, id: \.0) { date, dateTasks in
                Section(header: Text(date, style: .date)) {
                    ForEach(dateTasks) { task in
                        TaskRowView(task: task)
                    }
                }
            }
        }
    }
}

Thao tác CRUD với ModelContext

ModelContext thay thế NSManagedObjectContext cho tất cả các thao tác tạo, đọc, cập nhật và xóa.

CRUDOperations.swiftswift
import SwiftUI
import SwiftData

struct TaskManagementView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var tasks: [Task]
    @Query private var categories: [Category]

    @State private var newTaskTitle = ""
    @State private var selectedCategory: Category?

    var body: some View {
        NavigationStack {
            VStack {
                // Add form
                AddTaskForm(
                    title: $newTaskTitle,
                    category: $selectedCategory,
                    categories: categories,
                    onAdd: addTask
                )

                // Task list
                List {
                    ForEach(tasks) { task in
                        TaskRowView(task: task)
                            .swipeActions(edge: .trailing) {
                                Button(role: .destructive) {
                                    deleteTask(task)
                                } label: {
                                    Label("Delete", systemImage: "trash")
                                }
                            }
                            .swipeActions(edge: .leading) {
                                Button {
                                    toggleCompletion(task)
                                } label: {
                                    Label(
                                        task.isCompleted ? "Todo" : "Done",
                                        systemImage: task.isCompleted ? "circle" : "checkmark"
                                    )
                                }
                                .tint(task.isCompleted ? .orange : .green)
                            }
                    }
                }
            }
            .navigationTitle("Tasks")
        }
    }

    // CREATE
    private func addTask() {
        guard !newTaskTitle.isEmpty else { return }

        let task = Task(
            title: newTaskTitle,
            category: selectedCategory
        )

        // Insert into context
        modelContext.insert(task)

        // Explicit save (optional - autosave enabled by default)
        do {
            try modelContext.save()
        } catch {
            print("Save error: \(error)")
        }

        // Reset form
        newTaskTitle = ""
        selectedCategory = nil
    }

    // UPDATE
    private func toggleCompletion(_ task: Task) {
        // Direct modification - SwiftData tracks automatically
        task.isCompleted.toggle()

        // Automatic save handles persistence
    }

    // DELETE
    private func deleteTask(_ task: Task) {
        modelContext.delete(task)
    }
}

struct AddTaskForm: View {
    @Binding var title: String
    @Binding var category: Category?
    let categories: [Category]
    let onAdd: () -> Void

    var body: some View {
        VStack(spacing: 12) {
            TextField("New task...", text: $title)
                .textFieldStyle(.roundedBorder)

            HStack {
                Picker("Category", selection: $category) {
                    Text("None").tag(nil as Category?)
                    ForEach(categories) { cat in
                        Text(cat.name).tag(cat as Category?)
                    }
                }
                .pickerStyle(.menu)

                Button("Add", action: onAdd)
                    .buttonStyle(.borderedProminent)
                    .disabled(title.isEmpty)
            }
        }
        .padding()
    }
}

Kiểm thử Đơn vị với SwiftData

Một chiến lược kiểm thử vững chắc tạo điều kiện thuận lợi cho việc xác thực di chuyển. SwiftData cho phép tạo các container in-memory để kiểm thử.

SwiftDataTests.swiftswift
import XCTest
import SwiftData
@testable import TaskManager

final class TaskModelTests: XCTestCase {
    var container: ModelContainer!
    var context: ModelContext!

    override func setUpWithError() throws {
        // In-memory container for tests
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        container = try ModelContainer(
            for: Task.self, Category.self,
            configurations: config
        )
        context = ModelContext(container)
    }

    override func tearDownWithError() throws {
        container = nil
        context = nil
    }

    func testCreateTask() throws {
        // Given
        let task = Task(title: "Test Task")

        // When
        context.insert(task)
        try context.save()

        // Then
        let descriptor = FetchDescriptor<Task>()
        let tasks = try context.fetch(descriptor)

        XCTAssertEqual(tasks.count, 1)
        XCTAssertEqual(tasks.first?.title, "Test Task")
    }

    func testTaskCategoryRelationship() throws {
        // Given
        let category = Category(name: "Work", color: "blue")
        let task = Task(title: "Meeting", category: category)

        // When
        context.insert(category)
        context.insert(task)
        try context.save()

        // Then
        XCTAssertEqual(task.category?.name, "Work")
        XCTAssertTrue(category.tasks.contains(task))
    }

    func testDeleteCategoryCascade() throws {
        // Given
        let category = Category(name: "Personal")
        let task1 = Task(title: "Task 1", category: category)
        let task2 = Task(title: "Task 2", category: category)

        context.insert(category)
        context.insert(task1)
        context.insert(task2)
        try context.save()

        // When
        context.delete(category)
        try context.save()

        // Then - cascade delete should remove tasks
        let descriptor = FetchDescriptor<Task>()
        let remainingTasks = try context.fetch(descriptor)

        XCTAssertEqual(remainingTasks.count, 0)
    }

    func testFilteredFetch() throws {
        // Given
        let task1 = Task(title: "Completed", isCompleted: true)
        let task2 = Task(title: "Pending", isCompleted: false)
        let task3 = Task(title: "Also Pending", isCompleted: false)

        [task1, task2, task3].forEach { context.insert($0) }
        try context.save()

        // When
        var descriptor = FetchDescriptor<Task>(
            predicate: #Predicate { !$0.isCompleted }
        )
        let pendingTasks = try context.fetch(descriptor)

        // Then
        XCTAssertEqual(pendingTasks.count, 2)
    }
}

Danh sách Kiểm tra Di chuyển Đầy đủ

Dưới đây là tóm tắt các bước cho việc di chuyển thành công:

MigrationChecklist.swiftswift
/*
 PHASE 1: PREPARATION
 □ Audit Core Data features in use
 □ Identify features not supported by SwiftData
 □ Create dedicated migration branch
 □ Back up test data

 PHASE 2: MODEL CONVERSION
 □ Convert NSManagedObject entities to @Model
 □ Adapt relationships with @Relationship
 □ Configure appropriate delete rules
 □ Add required default values

 PHASE 3: CONFIGURATION
 □ Create ModelContainer with existing store URL
 □ Configure versioned schema if needed
 □ Define migration plan
 □ Test in coexistence mode if applicable

 PHASE 4: CODE MIGRATION
 □ Replace @FetchRequest with @Query
 □ Adapt predicates to #Predicate
 □ Migrate NSFetchedResultsController to manual grouping
 □ Convert CRUD operations to ModelContext

 PHASE 5: VALIDATION
 □ Run all unit tests
 □ Test migration with real data
 □ Verify performance with Instruments
 □ Validate CloudKit sync (if applicable)

 PHASE 6: DEPLOYMENT
 □ Document breaking changes
 □ Prepare rollback plan
 □ Deploy to TestFlight
 □ Monitor post-deployment crashes
*/

Kết luận

Việc di chuyển từ Core Data sang SwiftData là một bước tiến tự nhiên đối với các ứng dụng iOS hiện đại. Tính tương thích ở cấp độ store SQLite đảm bảo việc bảo toàn dữ liệu người dùng, trong khi cú pháp Swift gốc đơn giản hóa đáng kể mã nguồn.

Các Điểm Chính

  • ✅ SwiftData và Core Data chia sẻ cùng một engine SQLite
  • ✅ Sự cùng tồn tại cho phép di chuyển dần dần
  • @Query thay thế @FetchRequest với cú pháp đơn giản hơn
  • ✅ Các predicate động yêu cầu các mẫu thay thế
  • VersionedSchema quản lý sự phát triển của schema
  • ✅ Kiểm thử in-memory tạo điều kiện thuận lợi cho việc xác thực
  • ✅ iOS 26 mang lại hỗ trợ kế thừa lớp
  • ✅ Nên bắt đầu các dự án mới với SwiftData trừ khi có nhu cầu Core Data cụ thể

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thẻ

#swiftdata
#core-data
#ios
#migration
#swift

Chia sẻ

Bài viết liên quan