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

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

SwiftData ile iOS 17+ üretim deneyimi. Core Data'dan migration tuzakları, concurrency'de pratik gerçekler, performance baseline real numbers, ve neden bazı projelerde geri Core Data'ya döndük.

SwiftData Production: 6 Aylık Üretim Deneyimi ve Core Data'ya Geri Dönen 3 Senaryo

# 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

  1. SwiftData Nedir, Ne Değildir
  2. Core Data → SwiftData Migration Stratejisi
  3. @Model Macro: İç Mekanik
  4. Sorgulama: @Query vs FetchDescriptor vs Predicate DSL
  5. Concurrency: @ModelActor ve Tuzakları
  6. Performance Baseline — Gerçek Sayılar
  7. iCloud Sync: SwiftData CloudKit Entegrasyonu
  8. Core Data'ya Geri Dönen 3 Senaryo
  9. Migration Karar Matrisi
  10. 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 SwiftData
2@Model
3final class Prediction {
4 var id: UUID
5 var title: String
6 var createdAt: Date
7 
8 init(id: UUID = .init(), title: String, createdAt: Date = .now) {
9 self.id = id
10 self.title = title
11 self.createdAt = createdAt
12 }
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.self
4)

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@Model
2final class User {
3 var id: UUID
4 var email: String
5 @Attribute(.unique) var username: String
6 var posts: [Post] = []
7 
8 init(id: UUID = .init(), email: String, username: String) {
9 self.id = id
10 self.email = email
11 self.username = username
12 }
13}

Macro expansion:

  • User final class → NSManagedObject inherit
  • var id@NSManaged getter/setter + KVC keypath
  • @Attribute(.unique) → store-level unique constraint
  • var 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 = 50
6descriptor.propertiesToFetch = [\.title, \.excerpt] // partial fetch
7 
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-safe
3let pred = #Predicate<Post> { post in
4 post.title.localizedStandardContains(search) && post.year > 2023
5}

Compile-time errors, no string typos, auto-complete.

Tuzak: #Predicate Foundation closures (.contains, .starts(with:) etc.) destekliyor ama tüm Swift fonksiyonları desteklemiyor. Computed property kullanılamıyor — @Transient mark 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@ModelActor
2actor 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 uzakta
12let 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() // hata
3 
4// ✅ Doğru — ID return + main context'te re-fetch
5let ids: [PersistentIdentifier] = await processor.fetchAllIDs()
6let mainContext = container.mainContext
7let 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: modelConfiguration
8)

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_type SwiftData 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:

Etiketler

#SwiftData#Core Data#Persistence#iOS 17#Production#Migration#Concurrency
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ş

İlgili İçerik