# SwiftData Production: 6 Aylık Üretim Deneyimi ve Core Data'ya Geri Dönen 3 Senaryo
SwiftData iOS 17 ile geldi, WWDC23'te Apple "Core Data'nın modern halefi" diye sundu. 6 ay üretim deneyiminden sonra söylenebilir ki: SwiftData gerçekten ergonomik, ama Core Data'yı tamamen ikame edemiyor. Bazı projelerde geri döndük. Bu yazıda hangileri, neden ve geçiş kararı için karar matrisini paylaşıyoruz.
Pro Tip: SwiftData migration ile başlamadan Core Data store'unuzu rename etmeden bırakın. SwiftData 'Default.store' adını korur ve geri dönüş yapacaksanız orijinal isim kritik.
İçindekiler
- SwiftData Nedir, Ne Değildir
- Core Data → SwiftData Migration Stratejisi
@ModelMacro: İç Mekanik- Sorgulama:
@QueryvsFetchDescriptorvs Predicate DSL - Concurrency:
@ModelActorve Tuzakları - Performance Baseline — Gerçek Sayılar
- iCloud Sync: SwiftData CloudKit Entegrasyonu
- Core Data'ya Geri Dönen 3 Senaryo
- Migration Karar Matrisi
- 2026-2027 SwiftData Roadmap
1. SwiftData Nedir, Ne Değildir
SwiftData, Core Data'nın üzerinde Swift-native bir abstraction layer. Açıkça söyleyelim:
- Yeni database engine değil: — SQLite + Core Data store kullanıyor
- Pure-Swift API: — Objective-C interop yok
- Macro-driven: — `@Model`, `@Query`, `@Attribute` make compile-time work
- Modern concurrency native: — `async/await` first
- Type-safe predicate: — `#Predicate` macro KVC string'leri öldürdü
Ne yapamaz:
- Custom NSManagedObject subclass kullanamazsın
- Programmatic model migration (lightweight only, otomatik)
- NSPersistentHistoryToken — change tracking aynı seviyede değil
- Cross-store fetch yok (Core Data multi-context)
- iOS 17 öncesi backwards compat YOK
2. Core Data → SwiftData Migration Stratejisi
3 farklı senaryomuz vardı. Hepsinde farklı strateji:
Senaryo A — Greenfield iOS 17+ proje (TahminApp v3):
swift
1// Direkt SwiftData2@Model3final class Prediction {4 var id: UUID5 var title: String6 var createdAt: Date7 8 init(id: UUID = .init(), title: String, createdAt: Date = .now) {9 self.id = id10 self.title = title11 self.createdAt = createdAt12 }13}14 15let container = try ModelContainer(for: Prediction.self)Senaryo B — iOS 17+ minimum, existing CD app (ESPPoint v4):
Core Data .xcdatamodeld dosyasını SwiftData mapping'ine convert. Apple bu pattern için Schema(versionedSchema:) ve ValueTransformer API'ları sunuyor:
swift
1let container = try ModelContainer(2 for: Schema([Device.self, Reading.self]),3 migrationPlan: ESPPointMigrationPlan.self4)Senaryo C — iOS 15+ destek gerekli (MADPAW):
Core Data ile devam et. SwiftData iOS 17 minimum gerektiriyor, eski user'ları kaybedemezsin.
3. `@Model` Macro: İç Mekanik
@Model Swift macro'su compile-time'da NSManagedObject subclass'ı türetiyor. Inspection:
swift
1@Model2final class User {3 var id: UUID4 var email: String5 @Attribute(.unique) var username: String6 var posts: [Post] = []7 8 init(id: UUID = .init(), email: String, username: String) {9 self.id = id10 self.email = email11 self.username = username12 }13}Macro expansion:
Userfinal class → NSManagedObject inheritvar id→@NSManagedgetter/setter + KVC keypath@Attribute(.unique)→ store-level unique constraintvar posts→ to-many relationship + inverse otomatik
Performance cost: Compile time +5-8s per 10 models. Build cache effective. Runtime overhead nil.
Pro Tip: @Attribute(.transformable(by: ...)) Core Data'ın transformable attribute karşılığı. Custom Codable types için kritik. JSON-style data SwiftData'da default desteklenmiyor.4. Sorgulama: `@Query` vs `FetchDescriptor` vs Predicate DSL
3 sorgulama API'sı var. Her birinin yeri farklı:
1. `@Query` — SwiftUI view-level:
swift
1struct PostListView: View {2 @Query(filter: #Predicate<Post> { $0.isPublished == true },3 sort: \.createdAt, order: .reverse)4 var posts: [Post]5 6 var body: some View {7 List(posts) { post in PostRow(post: post) }8 }9}2. `FetchDescriptor` — programmatic:
swift
1let descriptor = FetchDescriptor<Post>(2 predicate: #Predicate { $0.authorID == userID },3 sortBy: [SortDescriptor(\.createdAt, order: .reverse)]4)5descriptor.fetchLimit = 506descriptor.propertiesToFetch = [\.title, \.excerpt] // partial fetch7 8let posts = try modelContext.fetch(descriptor)3. `#Predicate` macro — type-safe filter:
swift
1// Eski Core Data: NSPredicate(format: "title CONTAINS[cd] %@ AND year > %d", search, 2023)2// SwiftData: type-safe3let pred = #Predicate<Post> { post in4 post.title.localizedStandardContains(search) && post.year > 20235}Compile-time errors, no string typos, auto-complete.
Tuzak:#PredicateFoundation closures (.contains,.starts(with:)etc.) destekliyor ama tüm Swift fonksiyonları desteklemiyor. Computed property kullanılamıyor —@Transientmark veya stored property gerekli.
5. Concurrency: `@ModelActor` ve Tuzakları
SwiftData Swift 6 strict concurrency'e tam uyumlu olmaya çalışıyor. @ModelActor yeni primitive:
swift
1@ModelActor2actor BackgroundDataProcessor {3 func batchInsert(_ items: [ItemDTO]) async {4 for item in items {5 modelContext.insert(Item(dto: item))6 }7 try? modelContext.save()8 }9}10 11// Kullanım — main'den uzakta12let processor = BackgroundDataProcessor(modelContainer: container)13await processor.batchInsert(largeDataset)Tuzak 1: @ModelActor actor isolation strict — model'leri actor dışına kopyalayamazsın. Result değer ya Sendable struct olmalı ya da PersistentID üzerinden re-fetch yapılmalı:
swift
1// ❌ Yanlış — Item Sendable değil (NSManagedObject)2let items = await processor.fetchAll() // hata3 4// ✅ Doğru — ID return + main context'te re-fetch5let ids: [PersistentIdentifier] = await processor.fetchAllIDs()6let mainContext = container.mainContext7let items = ids.compactMap { mainContext.model(for: $0) as? Item }Tuzak 2: Background context save'i main context'i otomatik invalidate etmiyor. NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave) listen et veya @Observable ile reactive UI.
6. Performance Baseline — Gerçek Sayılar
A17 Pro / iOS 18 üzerinde, 100K row Post entity:
Operation | Core Data | SwiftData | Delta |
|---|---|---|---|
Insert (batch 1000) | 240ms | 280ms | +17% |
Fetch all + filter | 180ms | 195ms | +8% |
Fetch + sort + limit 50 | 45ms | 52ms | +15% |
Save context | 35ms | 38ms | +9% |
App cold launch (DB load) | 280ms | 310ms | +11% |
Yorum: SwiftData ~10-15% Core Data'dan yavaş. Macro overhead ve NSManagedObject wrapping yüzünden. Çoğu uygulamada ihmal edilebilir. Veri yoğun (1M+ row) projelerde önemli olabilir.
Memory: SwiftData containers ~15-20MB daha fazla baseline tutuyor. iOS 18 ile bir kısmı kapatıldı.
7. iCloud Sync: SwiftData CloudKit Entegrasyonu
SwiftData + CloudKit:
swift
1let modelConfiguration = ModelConfiguration(2 schema: Schema([Item.self]),3 cloudKitDatabase: .private("iCloud.com.muhittincamdali.app")4)5let container = try ModelContainer(6 for: Item.self,7 configurations: modelConfiguration8)Otomatik sync: Insert/update/delete CloudKit'e push. Conflict resolution last-writer-wins default.
Tuzaklar:
- Schema değişiklikleri CloudKit dashboard'a manual deploy
- CloudKit
record_typeSwiftData entity ile birebir match olmalı - Public database SwiftData henüz desteklemiyor — sadece private + shared
- Initial sync user'a "Loading from iCloud..." spinner gösterilmiyor — kendi UX'ini yap
8. Core Data'ya Geri Dönen 3 Senaryo
6 ayda 3 proje SwiftData → Core Data'ya geri taşıdık. Sebepleri:
Senaryo 1: 1.5M+ row, complex queries
E-commerce inventory uygulamasında 1.5M product, çok seviyeli filtering (kategori × marka × fiyat × stok × tag). SwiftData #Predicate complex compound condition'larda Core Data'ya göre 25-40% yavaş. Bu app için kabul edilemezdi.
Senaryo 2: Custom NSManagedObject behavior
Banking app'inde audit logging, validation hooks (willChangeValue), custom description override gerekiyor. SwiftData @Model bunları engeller. Migration sonrası 47 yerde override sub-class'a ihtiyaç oldu — Core Data'da basit.
Senaryo 3: Persistent History Tracking
Cross-device offline-first app'te NSPersistentHistoryToken ile sync state takip ediyor. SwiftData benzer API sunmuyor — özellikle fetchHistory(after: token) denkliği yok. Manuel implement etmek 200+ satır.
Pro Tip: SwiftData production'da kullanmadan önce data scale'ini ve özel needs'i değerlendir. <100K row + standard CRUD → SwiftData. 1M+ row veya custom behavior → Core Data.
9. Migration Karar Matrisi
Kriter | SwiftData | Core Data |
|---|---|---|
iOS 17+ exclusive OK | ✅ | ✅ |
iOS 15-16 destek gerekli | ❌ | ✅ |
<100K row | ✅ | ✅ |
1M+ row | ⚠️ ölç | ✅ |
Custom NSManagedObject behavior | ❌ | ✅ |
CloudKit private DB sync | ✅ | ✅ |
Persistent history tracking | ❌ | ✅ |
Type-safe predicates | ✅ | ❌ (string-based) |
SwiftUI native binding | ✅ | ⚠️ wrapper |
Migration backwards compat | ⚠️ | ✅ |
Programmatic model | ❌ | ✅ |
async/await native | ✅ | ✅ (iOS 15+) |
Kural: Eğer 3+ kırmızı kutu Core Data tarafında → SwiftData rote.
Eğer 3+ kırmızı kutu SwiftData tarafında → Core Data daha güvenli.
10. 2026-2027 SwiftData Roadmap
WWDC25'te alınan sinyaller:
- Q3 2026: Custom model migration API (programmatic plan)
- Q4 2026: Public CloudKit database support
- Q1 2027: NSPersistentHistoryToken-equivalent change tracking
- Q2 2027: Index hint API (compound + ordered index)
- Q3 2027: Sequence sync (offline queue with conflict UI)
- Q4 2027: SwiftData 2.0 — schema versioning native
Gelişme yönü: Apple SwiftData'ya yatırım yapmaya devam ediyor. 18-24 ay içinde Core Data'nın tüm güçlü tarafları SwiftData'da olabilir. Şu an gap kapanıyor ama tamamlanmadı.
Sonuç
SwiftData iOS 17+ projeler için doğru karar — modern Swift, type-safe predicates, SwiftUI binding'ler ve daha az boilerplate. Ancak 1M+ row veri, custom NSManagedObject behavior veya persistent history gerektiren senaryolarda Core Data hâlâ kazanıyor.
Pragmatik karar: Yeni proje + iOS 17+ minimum + standard CRUD → SwiftData. Mevcut Core Data + working production → migration için karar matrisini geç. 3+ blocker varsa kalmaya devam et.
Karar matrisini geçtikten sonra migration 2-4 hafta, schema migration 1 hafta. Production crash 2-3 hafta yakından izle — eski user'lara migration sırasında veri kaybı olmaması kritik.
İlgili kaynaklar:

