Tüm Yazılar
KategoriiOS
Okuma Süresi
22 dk
Yayın Tarihi
...
Kelime Sayısı
1.480kelime

Kahveni hazırla - bu içerikli bir makale!

Core Data migration stratejileri, batch operations, NSFetchedResultsController optimizasyonu, CloudKit sync ve WAL mode ile performance tuning.

Core Data İleri Seviye: Migration, Performance ve CloudKit Sync

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

  1. Core Data Stack Anatomisi
  2. Lightweight vs Heavyweight Migration
  3. Batch Operations ile Performance
  4. NSFetchedResultsController Optimizasyonu
  5. CloudKit Sync Entegrasyonu
  6. Concurrency ve Background Context
  7. Performance Tuning
  8. Core Data + SwiftUI
  9. Debugging ve Profiling
  10. 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 Stack
2class PersistenceController {
3 static let shared = PersistenceController()
4 
5 let container: NSPersistentContainer
6 
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.first
16 description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
17 description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
18 
19 container.loadPersistentStores { description, error in
20 if let error = error as NSError? {
21 fatalError("Core Data error: \(error), \(error.userInfo)")
22 }
23 }
24 
25 container.viewContext.automaticallyMergesChangesFromParent = true
26 container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
27 }
28 
29 // Background context - ağır işlemler için
30 func newBackgroundContext() -> NSManagedObjectContext {
31 let context = container.newBackgroundContext()
32 context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
33 return context
34 }
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 ekleme
4// ✅ Optional → Non-optional (default value ile)
5// ✅ Attribute yeniden adlandırma (Renaming ID ile)
6// ✅ Relationship ekleme
7 
8// Heavyweight Migration - manual mapping model gerekli
9// Gerekli durumlar:
10// ⚠️ Attribute tip değiştirme (String → Int)
11// ⚠️ Entity birleştirme/bölme
12// ⚠️ Karmaşık veri dönüşümleri
13 
14// Staged Migration (iOS 17+) - yeni API
15let container = NSPersistentContainer(name: "MyApp")
16let stages: [NSMigrationStage] = [
17 NSLightweightMigrationStage(["MyAppV1.mom", "MyAppV2.mom"]),
18 NSCustomMigrationStage(
19 migratingFrom: "MyAppV2",
20 to: "MyAppV3"
21 ) { context in
22 // Custom transformation logic
23 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 in
8 [
9 "id": product.id,
10 "name": product.name,
11 "price": product.price,
12 "updatedAt": Date()
13 ] as [String: Any]
14 }
15 )
16 batchInsert.resultType = .objectIDs
17 let result = try context.execute(batchInsert) as! NSBatchInsertResult
18 let objectIDs = result.result as! [NSManagedObjectID]
19 
20 // Main context'e bildir
21 NSManagedObjectContext.mergeChanges(
22 fromRemoteContextSave: [NSInsertedObjectsKey: objectIDs],
23 into: [PersistenceController.shared.container.viewContext]
24 )
25 }
26}
27 
28// Batch Update
29func 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 = .updatedObjectIDsResultType
36 try context.execute(batchUpdate)
37 }
38}
39 
40// Batch Delete
41func 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 = .resultTypeObjectIDs
48 try context.execute(batchDelete)
49 }
50}

NSFetchedResultsController Optimizasyonu {#nsfrc}

swift
1// Optimized NSFRC
2class 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 loading
15 request.relationshipKeyPathsForPrefetching = ["category"] // Prefetch
16 
17 frc = NSFetchedResultsController(
18 fetchRequest: request,
19 managedObjectContext: PersistenceController.shared.container.viewContext,
20 sectionNameKeyPath: "category.name",
21 cacheName: "products" // Disk cache
22 )
23 frc.delegate = self
24 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 sync
2class CloudPersistenceController {
3 let container: NSPersistentCloudKitContainer
4 
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 in
16 if let error { fatalError("CloudKit error: \(error)") }
17 }
18 
19 container.viewContext.automaticallyMergesChangesFromParent = true
20 
21 // Remote change notification
22 NotificationCenter.default.addObserver(
23 self,
24 selector: #selector(handleRemoteChange),
25 name: .NSPersistentStoreRemoteChange,
26 object: container.persistentStoreCoordinator
27 )
28 }
29 
30 @objc private func handleRemoteChange(_ notification: Notification) {
31 // CloudKit'ten yeni veri geldi
32 }
33}

Concurrency ve Background Context {#concurrency}

swift
1// ✅ Doğru: Background context ile ağır işlem
2func 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şlem
14func importOnMainThread(_ items: [DataItem]) {
15 let context = PersistenceController.shared.container.viewContext
16 // 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 entegrasyonu
2struct ProductListView: View {
3 @FetchRequest(
4 sortDescriptors: [SortDescriptor(\.name)],
5 predicate: NSPredicate(format: "isActive == YES"),
6 animation: .default
7 )
8 private var products: FetchedResults<CDProduct>
9 
10 var body: some View {
11 List(products) { product in
12 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 in
28 Section(section.id ?? "Other") {
29 ForEach(section) { product in
30 Text(product.name ?? "")
31 }
32 }
33 }
34 }
35 }
36}

Debugging ve Profiling {#debugging}

  1. Launch arguments: -com.apple.CoreData.SQLDebug 1 — SQL sorgularını logla
  2. Instruments: Core Data template ile fetch/save süreleri
  3. 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'si
2// CDProduct.xcdatamodeld → Product entity
3 
4// SwiftData modeli (aynı store'u kullanabilir)
5@Model
6class Product {
7 var name: String
8 var price: Decimal
9 var isActive: Bool
10 
11 init(name: String, price: Decimal, isActive: Bool = true) {
12 self.name = name
13 self.price = price
14 self.isActive = isActive
15 }
16}
17 
18// Coexistence: her ikisini de kullan
19// Core Data: eski ekranlar
20// SwiftData: yeni özellikler

Easter 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/)

Etiketler

#core-data#persistence#cloudkit#migration#nspersistentcontainer#ios
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz