SwiftData Tam Rehberi: @Model, @Query ve Modern Veri Kalıcılığı (iOS 17+, 2026)

SwiftData ile iOS uygulamalarınıza modern veri kalıcılığı ekleyin. @Model, @Query, ilişkiler ve şema göçü — çalışan kod örnekleriyle kapsamlı Türkçe rehber.

SwiftData Rehberi: @Model & @Query (iOS 17+ 2026)

Apple, iOS 17 ile birlikte veri kalıcılığı (persistence) konusunda oyunun kurallarını gerçekten değiştiren bir çerçeve tanıttı: SwiftData. Dürüst olmak gerekirse, WWDC 2023'te demoyu ilk izlediğimde biraz şüpheyle yaklaştım — Core Data'yı çok iyi bildiğimi düşünüyordum ve "yeni bir soyutlama katmanına gerçekten ihtiyacımız var mı?" diye sordum kendime. Yanıt: kesinlikle evet. Swift 5.9 makrolarından yararlanan bu modern çerçeve, inanılmaz derecede sade ve güçlü bir API sunuyor. 2026 itibarıyla SwiftData artık production-ready (üretime hazır) kabul ediliyor ve yeni iOS projelerinde Core Data yerine tercih edilen standart yaklaşım haline geldi.

Bu rehberde, sıfırdan bir iOS uygulaması inşa ederek SwiftData'nın tüm temel bileşenlerini adım adım inceleyeceğiz: @Model makrosuyla model tanımlama, ModelContainer kurulumu, @Query ile veri okuma, ModelContext ile CRUD işlemleri, ilişkiler, şema göçü ve performans en iyi uygulamaları. Hadi başlayalım.

SwiftData Nedir ve Neden Önemli?

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.

  1. Xcode'da .xcdatamodeld dosyasını açın → Editor menüsü → Create SwiftData Code seçin
  2. @FetchRequest kullanımlarını @Query ile değiştirin
  3. managedObjectContext environment değerini modelContext ile değiştirin
  4. NSPredicate ifadelerini #Predicate makrolarına dönüştürün
  5. 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.

Yazar Hakkında Priya Raghavan

Priya spent six years at Instacart building the iOS shopper app, where she led the migration from UIKit to SwiftUI across 80+ screens and cut crash-free sessions from 99.2% to 99.87%. Before that, she was a contractor at a Bay Area design studio shipping App Store apps for two Fortune 500 retail clients. She focuses on practical SwiftUI architecture - what holds up when you have 12 engineers committing to the same codebase, not just toy MVVM examples. Her recent work involves The Composable Architecture, Swift concurrency migration audits, and reducing main-thread hangs on older devices like the iPhone XR that enterprise fleets still ship. Priya runs a small consultancy in Oakland and occasionally speaks at try! Swift NYC. She has been writing Swift since the Objective-C bridging days of 2015.