Section 2/181 menit

2. Masalah yang Dipecahkan

2. Masalah yang Dipecahkan

Boilerplate Core Data yang Masif

Core Data mengharuskan developer mendefinisikan model di dua tempat: di .xcdatamodeld editor dan di kode Swift sebagai NSManagedObject subclass. Perubahan sekecil apapun di model harus disinkronkan manual di keduanya.

swift
// ❌ TANPA SwiftData: definisi model Core Data yang verbose
import CoreData

@objc(Task)
class Task: NSManagedObject {
    @NSManaged var id: UUID
    @NSManaged var title: String
    @NSManaged var isCompleted: Bool
    @NSManaged var createdAt: Date
    @NSManaged var priority: Int16
}

extension Task {
    @nonobjc class func fetchRequest() -> NSFetchRequest<Task> {
        return NSFetchRequest<Task>(entityName: "Task")
    }
}

// Setup stack di AppDelegate
lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "MyApp")
    container.loadPersistentStores { _, error in
        if let error { fatalError("Core Data error: \(error)") }
    }
    return container
}()

// Fetch dengan NSFetchRequest
func fetchTasks() -> [Task] {
    let request = Task.fetchRequest()
    request.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
    request.predicate = NSPredicate(format: "isCompleted == %@", false as CVarArg)
    return (try? context.fetch(request)) ?? []
}
swift
// ✓ DENGAN SwiftData: satu file, satu sumber kebenaran
import SwiftData

@Model
final class Task {
    var id: UUID
    var title: String
    var isCompleted: Bool
    var createdAt: Date
    var priority: Int

    init(title: String, priority: Int = 0) {
        self.id = UUID()
        self.title = title
        self.isCompleted = false
        self.createdAt = Date()
        self.priority = priority
    }
}

// Fetch type-safe di SwiftUI
@Query(sort: \Task.createdAt, order: .reverse)
private var tasks: [Task]

Predicate yang Tidak Type-Safe

NSPredicate menggunakan format string — error tidak terdeteksi sampai runtime.

swift
// ❌ TANPA SwiftData: runtime crash jika ada typo di format string
let predicate = NSPredicate(format: "titlee == %@", searchText) // typo, crash saat runtime
request.predicate = predicate

// ❌ Tidak ada autocomplete, tidak ada compile-time check
let predicate2 = NSPredicate(format: "priority > %d AND isComplted == YES", 2)
//                                                          ^ typo tidak terdeteksi compiler
swift
// ✓ DENGAN SwiftData: #Predicate ter-compile dan type-safe
let searchPredicate = #Predicate<Task> { task in
    task.title.contains(searchText)  // compiler langsung tahu 'title' adalah String
    && task.priority > 2              // compiler tahu 'priority' adalah Int
    && !task.isCompleted             // compiler tahu 'isCompleted' adalah Bool
}
// Typo di nama property → compile error, bukan runtime crash

Thread Safety yang Rumit

Core Data mengharuskan developer mengelola thread secara manual — NSManagedObjectContext tidak boleh diakses dari thread yang salah.

swift
// ❌ TANPA SwiftData: rawan data race
func processTasksBackground() {
    let context = persistentContainer.viewContext // viewContext: main thread only!
    DispatchQueue.global().async {
        // ⚠️ BAHAYA: akses context dari background thread
        let tasks = try? context.fetch(Task.fetchRequest())
        tasks?.forEach { $0.isCompleted = true }
        try? context.save() // crash atau data corruption
    }
}
swift
// ✓ DENGAN SwiftData + @ModelActor: isolation jelas, Swift concurrency-safe
@ModelActor
actor TaskProcessor {
    func markAllCompleted() async throws {
        let descriptor = FetchDescriptor<Task>(predicate: #Predicate { !$0.isCompleted })
        let tasks = try modelContext.fetch(descriptor)
        tasks.forEach { $0.isCompleted = true }
        try modelContext.save()
    }
}