Core Data, Apple'ın 2005'ten beri geliştirdiği ve milyonlarca uygulamanın kullandığı persistence framework'üdür. SwiftData yeni nesil olsa da, Core Data hala enterprise uygulamaların bel kemiği. Bu rehberde, temel CRUD'un ötesine geçip ileri seviye teknikleri öğreneceksin.
💡 Hızlı Not: Bu rehber Apple'ın Core Data Programming Guide ve WWDC21-24 session'larından derlendi. iOS 17+ NSPersistentContainer ve CloudKit entegrasyonu dahil.
İçindekiler
- Core Data Stack Anatomisi
- Lightweight vs Heavyweight Migration
- Batch Operations ile Performance
- NSFetchedResultsController Optimizasyonu
- CloudKit Sync Entegrasyonu
- Concurrency ve Background Context
- Performance Tuning
- Core Data + SwiftUI
- Debugging ve Profiling
- SwiftData'ya Migration
Core Data Stack Anatomisi {#stack-anatomisi}
Core Data stack'i dört ana bileşenden oluşur:
swift
1// Modern Core Data Stack2class PersistenceController {3 static let shared = PersistenceController()4 5 let container: NSPersistentContainer6 7 init(inMemory: Bool = false) {8 container = NSPersistentContainer(name: "MyApp")9 10 if inMemory {11 container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")12 }13 14 // WAL mode - concurrent read/write performansı15 let description = container.persistentStoreDescriptions.first16 description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)17 description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)18 19 container.loadPersistentStores { description, error in20 if let error = error as NSError? {21 fatalError("Core Data error: \(error), \(error.userInfo)")22 }23 }24 25 container.viewContext.automaticallyMergesChangesFromParent = true26 container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy27 }28 29 // Background context - ağır işlemler için30 func newBackgroundContext() -> NSManagedObjectContext {31 let context = container.newBackgroundContext()32 context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy33 return context34 }35}Stack Bileşenleri
Bileşen | Görev | Dikkat |
|---|---|---|
NSManagedObjectModel | Veri şeması (.xcdatamodeld) | Her migration'da güncelle |
NSPersistentStoreCoordinator | Store yönetimi | Thread-safe |
NSManagedObjectContext | Scratch pad (değişiklik takibi) | Main vs Background |
NSPersistentContainer | Hepsini saran convenience API | Modern tercih |
Lightweight vs Heavyweight Migration {#migration}
swift
1// Lightweight Migration - otomatik (model mapping gerekli değil)2// Desteklenen değişiklikler:3// ✅ Yeni attribute/entity ekleme4// ✅ Optional → Non-optional (default value ile)5// ✅ Attribute yeniden adlandırma (Renaming ID ile)6// ✅ Relationship ekleme7 8// Heavyweight Migration - manual mapping model gerekli9// Gerekli durumlar:10// ⚠️ Attribute tip değiştirme (String → Int)11// ⚠️ Entity birleştirme/bölme12// ⚠️ Karmaşık veri dönüşümleri13 14// Staged Migration (iOS 17+) - yeni API15let container = NSPersistentContainer(name: "MyApp")16let stages: [NSMigrationStage] = [17 NSLightweightMigrationStage(["MyAppV1.mom", "MyAppV2.mom"]),18 NSCustomMigrationStage(19 migratingFrom: "MyAppV2",20 to: "MyAppV3"21 ) { context in22 // Custom transformation logic23 let request = NSFetchRequest<NSManagedObject>(entityName: "User")24 let users = try context.fetch(request)25 for user in users {26 let fullName = "\(user.value(forKey: "firstName")!) \(user.value(forKey: "lastName")!)"27 user.setValue(fullName, forKey: "displayName")28 }29 try context.save()30 }31]Batch Operations ile Performance {#batch-operations}
10.000+ kayıt işlerken normal save() çok yavaş — batch operations kullan:
swift
1// Batch Insert - 10x hızlı2func batchInsertProducts(_ products: [ProductDTO]) async throws {3 let context = PersistenceController.shared.newBackgroundContext()4 try await context.perform {5 let batchInsert = NSBatchInsertRequest(6 entity: CDProduct.entity(),7 objects: products.map { product in8 [9 "id": product.id,10 "name": product.name,11 "price": product.price,12 "updatedAt": Date()13 ] as [String: Any]14 }15 )16 batchInsert.resultType = .objectIDs17 let result = try context.execute(batchInsert) as! NSBatchInsertResult18 let objectIDs = result.result as! [NSManagedObjectID]19 20 // Main context'e bildir21 NSManagedObjectContext.mergeChanges(22 fromRemoteContextSave: [NSInsertedObjectsKey: objectIDs],23 into: [PersistenceController.shared.container.viewContext]24 )25 }26}27 28// Batch Update29func markAllAsRead() async throws {30 let context = PersistenceController.shared.newBackgroundContext()31 try await context.perform {32 let batchUpdate = NSBatchUpdateRequest(entityName: "Notification")33 batchUpdate.predicate = NSPredicate(format: "isRead == NO")34 batchUpdate.propertiesToUpdate = ["isRead": true, "readAt": Date()]35 batchUpdate.resultType = .updatedObjectIDsResultType36 try context.execute(batchUpdate)37 }38}39 40// Batch Delete41func deleteOldLogs() async throws {42 let context = PersistenceController.shared.newBackgroundContext()43 try await context.perform {44 let fetchRequest: NSFetchRequest<NSFetchRequestResult> = CDLog.fetchRequest()45 fetchRequest.predicate = NSPredicate(format: "date < %@", Date().addingTimeInterval(-30*24*3600) as NSDate)46 let batchDelete = NSBatchDeleteRequest(fetchRequest: fetchRequest)47 batchDelete.resultType = .resultTypeObjectIDs48 try context.execute(batchDelete)49 }50}NSFetchedResultsController Optimizasyonu {#nsfrc}
swift
1// Optimized NSFRC2class ProductListViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {3 @Published var products: [CDProduct] = []4 private var frc: NSFetchedResultsController<CDProduct>!5 6 override init() {7 super.init()8 setupFRC()9 }10 11 private func setupFRC() {12 let request: NSFetchRequest<CDProduct> = CDProduct.fetchRequest()13 request.sortDescriptors = [NSSortDescriptor(keyPath: \CDProduct.name, ascending: true)]14 request.fetchBatchSize = 20 // ⚡ Lazy loading15 request.relationshipKeyPathsForPrefetching = ["category"] // Prefetch16 17 frc = NSFetchedResultsController(18 fetchRequest: request,19 managedObjectContext: PersistenceController.shared.container.viewContext,20 sectionNameKeyPath: "category.name",21 cacheName: "products" // Disk cache22 )23 frc.delegate = self24 try? frc.performFetch()25 products = frc.fetchedObjects ?? []26 }27 28 func controllerDidChangeContent(_ controller: NSFetchedResultsController<any NSFetchRequestResult>) {29 products = frc.fetchedObjects ?? []30 }31}CloudKit Sync Entegrasyonu {#cloudkit-sync}
swift
1// NSPersistentCloudKitContainer - otomatik sync2class CloudPersistenceController {3 let container: NSPersistentCloudKitContainer4 5 init() {6 container = NSPersistentCloudKitContainer(name: "MyApp")7 8 guard let description = container.persistentStoreDescriptions.first else { fatalError() }9 description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(10 containerIdentifier: "iCloud.com.myapp"11 )12 description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)13 description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)14 15 container.loadPersistentStores { _, error in16 if let error { fatalError("CloudKit error: \(error)") }17 }18 19 container.viewContext.automaticallyMergesChangesFromParent = true20 21 // Remote change notification22 NotificationCenter.default.addObserver(23 self,24 selector: #selector(handleRemoteChange),25 name: .NSPersistentStoreRemoteChange,26 object: container.persistentStoreCoordinator27 )28 }29 30 @objc private func handleRemoteChange(_ notification: Notification) {31 // CloudKit'ten yeni veri geldi32 }33}Concurrency ve Background Context {#concurrency}
swift
1// ✅ Doğru: Background context ile ağır işlem2func importLargeDataset(_ items: [DataItem]) async throws {3 let context = PersistenceController.shared.newBackgroundContext()4 try await context.perform {5 for item in items {6 let entity = CDItem(context: context)7 entity.configure(with: item)8 }9 try context.save()10 }11}12 13// ❌ Yanlış: Main context'te ağır işlem14func importOnMainThread(_ items: [DataItem]) {15 let context = PersistenceController.shared.container.viewContext16 // Bu UI'ı dondurur!17 for item in items { /* ... */ }18}Performance Tuning {#performance}
Teknik | Ne Yapar | Ne Zaman |
|---|---|---|
fetchBatchSize | Lazy loading | Her zaman |
fetchLimit | Sonuç sayısını kısıtla | Pagination |
propertiesToFetch | Sadece gerekli field'lar | Büyük entity'ler |
returnsObjectsAsFaults | Lazy property loading | Varsayılan açık |
relationshipKeyPathsForPrefetching | N+1 query önleme | İlişkili veriler |
NSBatchInsertRequest | Toplu ekleme | 100+ kayıt |
NSExpressionDescription | Aggregate (SUM, AVG) | İstatistikler |
Core Data + SwiftUI {#swiftui-integration}
swift
1// @FetchRequest ile SwiftUI entegrasyonu2struct ProductListView: View {3 @FetchRequest(4 sortDescriptors: [SortDescriptor(\.name)],5 predicate: NSPredicate(format: "isActive == YES"),6 animation: .default7 )8 private var products: FetchedResults<CDProduct>9 10 var body: some View {11 List(products) { product in12 ProductRow(product: product)13 }14 }15}16 17// SectionedFetchRequest (iOS 15+)18struct GroupedProductsView: View {19 @SectionedFetchRequest(20 sectionIdentifier: \.category?.name,21 sortDescriptors: [SortDescriptor(\.category?.name), SortDescriptor(\.name)]22 )23 private var sections: SectionedFetchResults<String?, CDProduct>24 25 var body: some View {26 List {27 ForEach(sections) { section in28 Section(section.id ?? "Other") {29 ForEach(section) { product in30 Text(product.name ?? "")31 }32 }33 }34 }35 }36}Debugging ve Profiling {#debugging}
- Launch arguments:
-com.apple.CoreData.SQLDebug 1— SQL sorgularını logla - Instruments: Core Data template ile fetch/save süreleri
- Xcode Debug: Core Data Memory Graph ile object graph
SwiftData'ya Migration {#swiftdata-migration}
SwiftData, Core Data'nın modern wrapper'ı. Kademeli geçiş mümkün:
swift
1// Core Data entity'si2// CDProduct.xcdatamodeld → Product entity3 4// SwiftData modeli (aynı store'u kullanabilir)5@Model6class Product {7 var name: String8 var price: Decimal9 var isActive: Bool10 11 init(name: String, price: Decimal, isActive: Bool = true) {12 self.name = name13 self.price = price14 self.isActive = isActive15 }16}17 18// Coexistence: her ikisini de kullan19// Core Data: eski ekranlar20// SwiftData: yeni özelliklerEaster Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [Apple: Core Data Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/index.html) - [WWDC21: Bring Core Data concurrency to Swift and SwiftUI](https://developer.apple.com/videos/play/wwdc2021/10017/) - [WWDC23: Model your schema with SwiftData](https://developer.apple.com/videos/play/wwdc2023/10187/)

