SwiftData, Apple'ın 2023 WWDC'de tanıttığı ve iOS 17+ platformlarını hedefleyen modern bir nesne grafiği ve kalıcılık çerçevesidir. Teknik olarak Core Data altyapısı üzerine inşa edilmiştir — aynı SQLite depolama motorunu ve iCloud senkronizasyon mekanizmasını kullanır. Ancak tüm bu karmaşıklığı zarif bir Swift API'sinin ardına gizler. (Yani altta yatan sihir aynı; sadece çok daha güzel bir ambalaja kavuştu.)
Eski Core Data yaklaşımında bir entity tanımlamak için .xcdatamodeld dosyasına görsel düzenleyicide çizim yapmanız, NSManagedObject alt sınıfları oluşturmanız ve bağlam (context) yönetimini manuel halletmeniz gerekiyordu. Hatırlıyorum, ilk Core Data projem tam bir kâbustu. SwiftData'da ise tek bir @Model makrosu tüm bunları sizin için yapıyor.
SwiftData'nın sağladığı temel avantajlar şunlardır:
- Kod-öncelikli yaklaşım:
.xcdatamodeld dosyasına ihtiyaç yok; şema doğrudan Swift kodunuzdan türetilir
- Az standart kod: Core Data'nın gerektirdiği NSPersistentContainer, NSManagedObjectContext ve NSFetchRequest kurulumu yok
- SwiftUI ile derin entegrasyon:
@Query makrosu veriler değiştikçe görünümü otomatik günceller
- Tür güvenliği:
#Predicate makrosu yanlış özellik adlarını derleme zamanında yakalar
- iCloud senkronizasyonu: CloudKit entegrasyonu kutudan çıkar çıkmaz çalışır
Gereksinimler
SwiftData kullanmak için projenizin şu gereksinimleri karşılaması gerekir:
- iOS 17.0 veya üzeri (minimum hedef platform)
- Xcode 15 veya üzeri
- Swift 5.9 veya üzeri
Ek bir paket kurmanıza gerek yok — SwiftData, Apple platform SDK'sına dahildir. Tek yapmanız gereken şunu eklemek:
import SwiftData
@Model Makrosu ile Veri Modeli Tanımlama
SwiftData'da her şey @Model makrosuyla başlar. Herhangi bir Swift sınıfına bu makroyu eklemek, onu kalıcı bir veri modeline dönüştürür — bu kadar basit. Arka planda makro; veritabanı şeması oluşturma, değişiklik takibi (change tracking) ve SwiftUI entegrasyonu için gereken tüm kodu otomatik olarak üretir.
import SwiftData
@Model
class Gorev {
var id: UUID
var baslik: String
var tamamlandi: Bool
var olusturmaTarihi: Date
init(baslik: String) {
self.id = UUID()
self.baslik = baslik
self.tamamlandi = false
self.olusturmaTarihi = Date()
}
}
Bu basit tanımlama, SwiftData'ya modeli yüklemek, kaydetmek, sorgulamak ve silmek için ihtiyacı olan her şeyi verir. NSManagedObject kalıtımı yok, özel property descriptor'lar yok, XML tabanlı mapping yok. Gerçekten bu kadar.
@Attribute ile Özellik Özelleştirme
Belirli özelliklere ek kısıtlar veya davranışlar tanımlamak için @Attribute makrosunu kullanabilirsiniz. İki en sık kullanılan seçenek şunlardır:
@Model
class Kullanici {
@Attribute(.unique) var email: String // Benzersizlik kısıtı
@Attribute(.externalStorage) var avatar: Data? // Büyük veriyi dışarıda tut
var ad: String
var kayitTarihi: Date
init(email: String, ad: String) {
self.email = email
self.ad = ad
self.kayitTarihi = Date()
}
}
.unique kısıtı aynı e-posta adresiyle iki kullanıcı oluşturulmasını engeller. .externalStorage ise resimler gibi büyük binary veriyi veritabanı dışında ayrı dosyalarda saklayarak sorgu performansını korur. (Uygulama boyutu büyük tutanlara bakıyorum — bu özelliği mutlaka kullanın.)
ModelContainer: Veritabanı Altyapısını Kurma
Peki tüm bu modelleri nereye bağlıyoruz? İşte ModelContainer burada devreye giriyor. Bu, SwiftData'nın depolama katmanıdır; arka planda SQLite dosyasını yöneten ve şema bilgisini tutan bileşendir. SwiftUI uygulamasında bunu genellikle @main App struct'ında tanımlar ve tüm görünüm hiyerarşisine aktarırsınız:
import SwiftUI
import SwiftData
@main
struct GorevUygulamasi: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [Gorev.self, Kullanici.self])
}
}
.modelContainer(for:) modifier'ı gerekli tüm altyapıyı kurar, SQLite dosyasını oluşturur (veya varsa yükler) ve container'ı SwiftUI environment'ına enjekte eder.
Test için Bellek İçi Depolama
Birim testlerinde gerçek bir veritabanı dosyası oluşturmak yerine bellek içi depolama kullanın. Her test çalıştırmasında temiz bir durum elde edersiniz — bu gerçekten büyük bir kolaylık:
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(
for: Gorev.self,
configurations: config
)
// Test'e container'ı inject edin
let view = ContentView()
.modelContainer(container)
@Query ile Veri Okuma ve Reaktif UI
İşte asıl sihir burada. @Query, SwiftData'nın en güçlü özelliğidir. Core Data'daki @FetchRequest'in modern, tür-güvenli versiyonudur. Veriler değiştikçe SwiftUI görünümünü otomatik olarak yeniden render eder; herhangi bir bildirim veya delegate kurulumuna ihtiyaç duymazsınız.
import SwiftUI
import SwiftData
struct GorevListesiView: View {
@Query var gorevler: [Gorev]
var body: some View {
NavigationStack {
List(gorevler) { gorev in
HStack {
Image(systemName: gorev.tamamlandi
? "checkmark.circle.fill"
: "circle")
.foregroundStyle(gorev.tamamlandi ? .green : .secondary)
Text(gorev.baslik)
}
}
.navigationTitle("Görevler")
}
}
}
Sıralama ve Filtreleme
@Query'ye sıralama ve filtreleme parametreleri ekleyebilirsiniz. Bu özellik özellikle büyük veri setlerinde hayat kurtarır:
// Tarihe göre azalan sıralama
@Query(sort: \Gorev.olusturmaTarihi, order: .reverse)
var gorevler: [Gorev]
// Sadece tamamlanmamış görevleri getir
@Query(filter: #Predicate<Gorev> { gorev in
gorev.tamamlandi == false
})
var aktifGorevler: [Gorev]
// Birden fazla koşul
@Query(
filter: #Predicate<Gorev> { $0.tamamlandi == false },
sort: \Gorev.olusturmaTarihi,
order: .forward
)
var siralıAktifGorevler: [Gorev]
#Predicate makrosu, Core Data'nın NSPredicate'inin tür-güvenli karşılığıdır. Yanlış özellik adı yazarsanız çalışma zamanı hatası değil, derleme hatası alırsınız. Bu küçük bir fark gibi görünse de, prodüksiyonda beklenmedik çökme yaşamak yerine derleme sırasında hatayı yakalamak son derece değerlidir.
Dinamik Filtreler
İşte ilginç bir kısım: kullanıcı girişine göre değişen filtreler için @Query'yi view'ın init'inde yapılandırabilirsiniz:
struct GorevListesiView: View {
@Query var gorevler: [Gorev]
init(sadeceAktif: Bool) {
let aktif = sadeceAktif
_gorevler = Query(
filter: #Predicate<Gorev> { gorev in
aktif ? gorev.tamamlandi == false : true
},
sort: \Gorev.olusturmaTarihi,
order: .reverse
)
}
var body: some View {
List(gorevler) { gorev in
Text(gorev.baslik)
}
}
}
ModelContext: Veri Ekleme, Güncelleme ve Silme
ModelContext, verilerle çalıştığınız canlı ortamdır. Değişiklikler önce bellekte context'te tutulur, ardından SwiftData tarafından otomatik olarak veritabanına kaydedilir. Elle save() çağırmanıza genellikle gerek yoktur — bu gerçekten büyük bir rahatlama.
Veri Ekleme
struct YeniGorevView: View {
@Environment(\.modelContext) private var modelContext
@State private var baslik = ""
var body: some View {
Form {
TextField("Görev başlığı", text: $baslik)
Button("Ekle") {
guard !baslik.trimmingCharacters(in: .whitespaces).isEmpty
else { return }
let yeniGorev = Gorev(baslik: baslik)
modelContext.insert(yeniGorev)
baslik = ""
}
}
}
}
Veri Güncelleme
Güncelleme için @Model sınıfının özelliğini doğrudan değiştirmeniz yeterlidir — SwiftData değişikliği otomatik algılar. Bundan daha sade olamaz:
// Bir görevin tamamlanma durumunu değiştir
gorev.tamamlandi.toggle()
// Kaydedildi! Elle save() gerekmez.
Veri Silme
struct GorevListesiView: View {
@Query var gorevler: [Gorev]
@Environment(\.modelContext) private var modelContext
var body: some View {
List {
ForEach(gorevler) { gorev in
Text(gorev.baslik)
}
.onDelete { indeksler in
for indeks in indeksler {
modelContext.delete(gorevler[indeks])
}
}
}
}
}
// Toplu silme: tamamlanan tüm görevleri temizle
try modelContext.delete(
model: Gorev.self,
where: #Predicate { $0.tamamlandi == true }
)
İlişkiler: @Relationship ile Modeller Arası Bağlantı
Gerçek uygulamalar genellikle birbiriyle ilişkili modeller içerir. SwiftData'da ilişkileri @Relationship makrosuyla tanımlarsınız. Core Data'daki gibi görsel bir editörde sürükle-bırak gerekmez:
@Model
class Proje {
var ad: String
var olusturmaTarihi: Date
@Relationship(deleteRule: .cascade) var gorevler: [Gorev] = []
init(ad: String) {
self.ad = ad
self.olusturmaTarihi = Date()
}
}
@Model
class Gorev {
var baslik: String
var tamamlandi: Bool
var proje: Proje? // Ters ilişki — SwiftData otomatik yönetir
init(baslik: String) {
self.baslik = baslik
self.tamamlandi = false
}
}
deleteRule: .cascade, bir Proje silindiğinde tüm ilişkili Görevlerin de otomatik silineceği anlamına gelir. Mevcut silme kuralları:
.nullify (varsayılan): İlişkili nesneler silinmez, yalnızca ilişki null'a ayarlanır
.cascade: Üst nesne silindiğinde ilişkili nesneler de silinir
.deny: İlişkili nesneler varken silme işlemini engeller
.noAction: Hiçbir ek işlem yapılmaz
#Unique ile Bileşik Benzersizlik Kısıtları (iOS 18+)
iOS 18 ile gelen #Unique makrosu, birden fazla özelliğin birleşiminin benzersiz olmasını zorunlu kılar. Çakışma oluştuğunda SwiftData otomatik upsert (varsa güncelle, yoksa ekle) uygular — bu özellikle API senkronizasyonu senaryolarında çok işe yarar:
@Model
#Unique<Urun>([\.barkod], [\.ad, \.kategori])
class Urun {
var barkod: String
var ad: String
var kategori: String
var fiyat: Double
init(barkod: String, ad: String, kategori: String, fiyat: Double) {
self.barkod = barkod
self.ad = ad
self.kategori = kategori
self.fiyat = fiyat
}
}
Bu kısıtla, aynı barkoda sahip iki ürün veya aynı (ad + kategori) kombinasyonuna sahip iki ürün kaydedemezsiniz.
Şema Göçü (Schema Migration)
Şema göçleri her iOS geliştiricisinin bir noktada yüzleşmek zorunda kaldığı konulardan biridir. Uygulamanızı yayınladıktan sonra veri modelini değiştirmeniz kaçınılmazdır. SwiftData bunu VersionedSchema ve SchemaMigrationPlan protokolleriyle yönetir:
// v1 şeması
enum GorevSemasi_v1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] = [Gorev.self]
@Model final class Gorev {
var baslik: String
init(baslik: String) { self.baslik = baslik }
}
}
// v2 şeması — öncelik alanı eklendi
enum GorevSemasi_v2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] = [Gorev.self]
@Model final class Gorev {
var baslik: String
var oncelik: Int // YENİ
init(baslik: String, oncelik: Int = 0) {
self.baslik = baslik
self.oncelik = oncelik
}
}
}
// Göç planı
enum GorevGocPlani: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] = [
GorevSemasi_v1.self,
GorevSemasi_v2.self
]
static var stages: [MigrationStage] = [
.lightweight(
fromVersion: GorevSemasi_v1.self,
toVersion: GorevSemasi_v2.self
)
]
}
// App'e bağla
.modelContainer(
for: Gorev.self,
migrationPlan: GorevGocPlani.self
)
Core Data'dan SwiftData'ya Geçiş
Mevcut Core Data projenizi SwiftData'ya taşımak istiyorsanız aşağıdaki adımları takip edin. İkisi yan yana çalışabilir; bu nedenle acele etmenize gerek yok — kademeli, planlı bir geçiş her zaman daha güvenlidir.
- Xcode'da
.xcdatamodeld dosyasını açın → Editor menüsü → Create SwiftData Code seçin
@FetchRequest kullanımlarını @Query ile değiştirin
managedObjectContext environment değerini modelContext ile değiştirin
- NSPredicate ifadelerini
#Predicate makrolarına dönüştürün
- NSPersistentContainer kurulumunu kaldırın, ModelContainer ekleyin
SwiftData ile geliştirdiğiniz uygulamalarda modern async/await yapısıyla veri işlemleri yapmak istiyorsanız, Swift 6.2 yaklaşılabilir eşzamanlılık rehberimiz bu iki teknolojiyi nasıl bir arada kullanacağınızı adım adım açıklıyor.
Arka Plan Veri İşlemleri: @ModelActor
Büyük veri içe aktarma işlemlerinde ana iş parçacığını (main thread) bloke etmemek kritik önem taşır — bunu ihmal ettiğinizde kullanıcı donmuş bir arayüzle karşılaşır. SwiftData'nın @ModelActor makrosu bu sorunu zarif biçimde çözer:
@ModelActor
actor VeriAktaricisi {
func topluGorevEkle(basliklar: [String]) async throws {
for baslik in basliklar {
let gorev = Gorev(baslik: baslik)
modelContext.insert(gorev)
}
try modelContext.save()
}
}
// Kullanım
Task {
let aktarici = VeriAktaricisi(modelContainer: container)
try await aktarici.topluGorevEkle(
basliklar: ["Belgeleri güncelle", "PR yaz", "Test et"]
)
}
Performans İpuçları
Birkaç basit kurala uyduğunuzda SwiftData son derece performanslı çalışır. İşte dikkat etmeniz gerekenler:
Büyük Veri Setlerinde Filtreleme Zorunlu
Filtresiz @Query tüm nesneleri belleğe yükler ve arayüzde donmalara yol açabilir. Her zaman #Predicate ve sort parametrelerini kullanın.
Büyük Binary Veriyi Dışarıda Saklayın
Resim, video veya ses dosyası gibi büyük veri için @Attribute(.externalStorage) kullanın. Veritabanı boyutunu küçük tutar, sorgu hızını artırır.
Lazy Loading'den Yararlanın
SwiftData ilişkili nesneleri varsayılan olarak tembel (lazy) yükler. Gereksiz ilişkilere erişmekten kaçının; bu, gereksiz veritabanı sorgularını önler.
Xcode 26'nın gelişmiş hata ayıklama ve profilleme araçları, SwiftData performansını optimize etmek için son derece kullanışlıdır. Xcode 26.3 ajan tabanlı kodlama rehberimizde bu araçların nasıl etkin kullanılacağını öğrenebilirsiniz.
SwiftData mi, Core Data mi?
Doğru seçim büyük ölçüde hedef iOS sürümünüze bağlıdır. Benim kişisel önerim: eğer iOS 17+ hedefliyorsanız, yeni projelerde SwiftData'ya tereddütsüz geçin.
- iOS 17+ hedefliyorsanız: Yeni projeler için SwiftData kullanın. Daha az kod, daha iyi SwiftUI entegrasyonu ve modern bir API sunuyor.
- iOS 15–16 desteği gerekiyorsa: Core Data ile devam edin veya her ikisini birlikte kullanın.
- Mevcut Core Data projesi: Aceleyle tam geçiş yapmayın. İkisi aynı anda çalışabilir; kademeli, planlı bir geçiş daha güvenlidir.
Sıkça Sorulan Sorular
SwiftData, Core Data'yı tamamen değiştiriyor mu?
Teknik olarak SwiftData, Core Data altyapısı üzerine kurulmuştur ve onu tamamen ortadan kaldırmaz. Apple yeni projelerde SwiftData kullanımını açıkça teşvik etse de Core Data, iOS 15–16 desteği gereken mevcut projeler için geçerliliğini korumaktadır.
SwiftData ile iCloud senkronizasyonu nasıl yapılır?
SwiftData, CloudKit ile yerleşik entegrasyon sunar. ModelContainer oluştururken cloudKitDatabase parametresini ayarlamanız yeterlidir. Core Data'daki karmaşık NSPersistentCloudKitContainer kurulumuna gerek yoktur.
@Query ile karmaşık filtreler yazabilir miyim?
Evet, #Predicate makrosuyla AND, OR, CONTAINS, IN gibi koşullar tanımlayabilirsiniz. iOS 18, filtreleme yeteneklerini önemli ölçüde genişletti; özellikle ilişkili modeller üzerinde sorgular artık çok daha güçlü.
SwiftData UIKit projesinde de kullanılabilir mi?
Evet, SwiftData yalnızca SwiftUI ile sınırlı değildir. UIKit projelerinde ModelContext'i doğrudan kullanabilirsiniz. Ancak @Query yalnızca SwiftUI view'larında çalışır; UIKit'te FetchDescriptor ile manuel sorgulama yapmanız gerekir.
SwiftData şema göçü (migration) ne zaman gereklidir?
Model sınıflarınıza yeni bir özellik eklemek veya mevcut özellikleri yeniden adlandırmak istediğinizde şema göçüne ihtiyaç duyarsınız. Yeni alan eklemek gibi basit değişiklikler için hafif göç (lightweight migration) yeterlidir; karmaşık veri dönüşümleri için özel göç fonksiyonları yazabilirsiniz.