Tüm Yazılar
KategoriiOS
Okuma Süresi
14 dk
Yayın Tarihi
2026-06-26
Kelime Sayısı
2.840kelime

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

SwiftData iOS 27: Sectioned Query, .codable, ResultsObserver

Özet

iOS 27 SwiftData sectioned query, .codable attribute ve ResultsObserver ile neler değişti? WWDC26 oturum 274'ten kaynaklı, kod örnekli rehber.

SwiftData iOS 27: Sectioned Query, .codable, ResultsObserver

iOS 27 ile SwiftData'ya WWDC26'nın 274 numaralı oturumunda dört yeni yetenek geldi: sectioned query, .codable attribute, ResultsObserver ve HistoryObserver. Bu yazıda SwiftData iOS 27 sectioned query özelliğini @Query üzerinden nasıl kullanacağını, .codable ile kendi sahibi olmadığın tipleri nasıl saklayacağını ve SwiftUI dışında ResultsObserver ile sorgu sonuçlarını nasıl dinleyeceğini adım adım göreceksin. Dördünü de WWDC26 oturum 274 transkripti ve Apple'ın resmi SwiftData dokümantasyonu üzerinden, beta döneminde (Haziran 2026) geçerli haliyle anlatıyorum.

💡 Pro Tip: .codable attribute'u kendi tanımladığın modellerde değil, yalnızca sahibi olmadığın (ör. bir framework'ün) tiplerini saklarken kullan — kendi tiplerin için tam @Model desteği (filtre, sıralama, index) her zaman daha güçlü.

İçindekiler

iOS 27 SwiftData Deltası: WWDC26 Oturum 274

Apple, WWDC26'nın 274 numaralı oturumunda SwiftData'ya dört ekleme tanıttı: sorgu sonuçlarını bölümlere ayırma (sectioning), özel/üçüncü taraf tiplerin Codable üzerinden kalıcılaştırılması, ResultsObserver ile SwiftUI dışında herhangi bir yerde sorgu gözlemleme ve HistoryObserver ile veri deposu değişikliklerini izleme. Bu dört yetenek Apple'ın resmi "SwiftData updates" sayfasında da aynı sırayla listeleniyor; oturumun kendisi anlatıcının ifadesiyle "Apple'ın 2027 sürümleri"ndeki SwiftData yenilikleri olarak sunuluyor — yani iOS 27 ve macOS 27 (Golden Gate) döngüsü.

Bu yazı, henüz beta döneminde (26 Haziran 2026 itibarıyla) geçerli olan API yüzeyini anlatıyor. Aynı döngüde Core Data tarafında herhangi bir yeni özellik veya duyuru yok; object-ID interop hâlâ eksik. Bu yüzden SwiftData'yı halihazırda production'da kullanıyorsan bu dört ekleme seni doğrudan ilgilendiriyor — özet ve derinlemesine örnekler aşağıda.

Apple oturumun iki cümlelik tanıtım metninde dört yeteneği özetliyor: özel ve üçüncü taraf tipleri Codable üzerinden kalıcılaştırmayı, fetch edilen veriyi section'lara ayırmayı ve ResultsObserver ile HistoryObserver üzerinden veri deposu değişikliklerini SwiftUI dışındaki her yerde gözlemlemeyi göstereceklerini söylüyor. Bu dördünün aynı oturumda ve aynı "what's new" sayfasında gruplanmış olması tesadüf değil — hepsi SwiftData'yı SwiftUI'nin dışına, servis katmanlarına ve karmaşık liste UI'larına taşımayı hedefliyor.

iOS 27'de enum değerleriyle #Predicate filtrelemeye dair ne WWDC26 oturum 274'te ne de Apple'ın resmi SwiftData dokümantasyon sayfalarında (Query macros, Predicate, Schema.Attribute.Option) bir duyuru var; bu yüzden bu yazı enum predicate'leri kapsamıyor. Aşağıdaki dört bölüm, oturumda ve dokümantasyonda birebir karşılığı olan bu dört eklemeyi sırayla ele alıyor.

Sectioned Query: Liste Gruplamayı Sorguya Taşımak

iOS 27'den önce SwiftData ile bir listeyi bölümlere ayırmak istediğinde (örneğin gezileri hedef şehre göre gruplamak) fetch edilen tüm sonucu elle Dictionary(grouping:by:) ile gruplamak veya Section view'larını manuel doldurmak gerekiyordu. @Query'ye eklenen sectionBy parametresi bu işi doğrudan sorguya taşıyor: modelin kök tipinden başlayan bir KeyPath veriyorsun, SwiftData sonuçları bu değere göre gruplayıp sana hazır section koleksiyonu veriyor.

swift
1import SwiftData
2import SwiftUI
3 
4struct TripListView: View {
5 @Query(sort: \Trip.startDate, sectionBy: \Trip.destination)
6 private var trips: [Trip]
7 
8 var body: some View {
9 List {
10 ForEach(_trips.sections) { section in
11 Section(section.id) {
12 ForEach(section) { trip in
13 Text(trip.name)
14 }
15 }
16 }
17 }
18 }
19}

Property wrapper'ın alt çizgili adıyla (_trips) erişilen .sections koleksiyonu, her biri id olarak sectionBy KeyPath'inin değerini taşıyan section'lardan oluşuyor. Gruplama artık sorgudan geldiği için Dictionary(grouping:by:) ile elle gruplama ve section listesini senkron tutma işi ortadan kalkıyor; SwiftUI tarafında yalnızca dış ForEach'i section'lar üzerinde, iç ForEach'i section'ın kendisi üzerinde kuruyorsun. Section'ın kendisi bir koleksiyon olduğu için iç ForEach'e doğrudan section'ı verebiliyorsun, ayrıca bir elements dizisine gitmen gerekmiyor.

Apple'ın resmi dokümantasyonu bu API'yi "Additional query macros" başlığı altında somut overload'larla yayınladı: Query(_:animation:sectionBy:) ve Query(filter:sort:transaction:sectionBy:) gibi imzalar mevcut. Yani sectioning, filtreleme ve sıralamayla birlikte aynı makroda kullanılabiliyor — ayrı bir API öğrenmen gerekmiyor.

Sectioned Query Ne Zaman İşe Yarar?

Senaryo
Sectioned query önerilir mi?
Neden
Sabit, az sayıda grup (ör. durum: aktif/tamamlanmış)
Evet
Section id'leri sorgudan geliyor, elle senkron tutman gerekmiyor
Gruplama anahtarı sık değişiyor (ör. arama sonucu)
Dikkatli kullan
Her değişiklikte section koleksiyonu yeniden hesaplanıyor
Gruplama, .codable bir alana göre
Hayır
Apple bunu ayrıca belirtmiyor; codable içeriğin SwiftData'ya opak olması gruplamanın da desteklenmeyeceği beklentisini doğuruyor — güvenli yol, gruplama anahtarını ayrı bir native String alanda tutmak

Pratikte sectioning eklemeye karar verirken kendine şu soruları sorman yeterli:

  • Grup sayısı sınırlı mı?: Durum, kategori, öncelik gibi az sayıda sabit değerse sectioning UI kodunu ciddi şekilde sadeleştiriyor.
  • Gruplama anahtarı String mi?: sectionBy KeyPath'i String ya da String? bir alana çıkmalıdır; enum veya Date ile gruplayacaksan modeline hesaplanmış bir String alan eklemen gerekiyor.
  • Sıralamayla birlikte mi kullanılacak?: Query(filter:sort:transaction:sectionBy:) overload'ı sayesinde sectioning'i mevcut sort parametrenle aynı sorguda birleştirebiliyorsun, ayrı bir fetch yazmana gerek kalmıyor.

.codable Attribute: Kendi Sahibi Olmadığın Tipleri Saklamak

SwiftData bugüne kadar bir model özelliğini saklayabilmesi için ya native desteklediği bir tip (String, Int, Date, küçük struct'lar) olmasını ya da senin Codable uyumlu kendi tipini tanımlamanı bekliyordu. iOS 27 ile gelen .codable seçeneği, üçüncü bir yol açıyor: sahibi olmadığın (örneğin bir framework'ün tanımladığı) Codable uyumlu bir tipi, SwiftData'nın şema çıkarımı yapmasına gerek kalmadan doğrudan saklayabiliyorsun.

swift
1import MapKit
2import SwiftData
3 
4@Model
5final class Trip {
6 var name: String
7 var startDate: Date
8 var destination: String
9 
10 @Attribute(.codable)
11 var externalLocation: MKMapItem.Identifier?
12 
13 init(name: String, startDate: Date, destination: String) {
14 self.name = name
15 self.startDate = startDate
16 self.destination = destination
17 }
18}

Schema.Attribute.Option üzerinde static var codable: Schema.Attribute.Option { get } olarak tanımlı bu seçenek, SwiftData'ya serileştirmeyi tipin kendi Codable implementasyonuna devretmesini söylüyor.

Apple, WWDC26 oturumunda bunu net bir dille konumlandırıyor: .codable kendi tanımladığın tipler için önerilen yol değil, SwiftData'nın native desteklemediği tipleri kalıcılaştırmak için bir "kaçış kapağı" (escape hatch). Kendi sahip olduğun tipler için tam @Model desteğiyle (filtre, sıralama, index) modelleme her zaman tercih edilmeli.

.codable'ın Sınırları: Predicate, Sort ve Migration

.codable attribute'ların içeriği SwiftData'ya opak kalıyor — bu, iki somut sınırlama getiriyor. Transkriptteki ifadeyle: "the contents of codable attributes are opaque to SwiftData... they can't be used in Predicates to filter results or for sorting — using Sort Descriptors." Yani #Predicate içinde externalLocation alanına göre filtreleme ya da SortDescriptor ile sıralama yapamıyorsun; bu alanlar yalnızca oku/yaz amaçlı kalıyor.

İkinci sınırlama migration tarafında — ama bu sefer lehine işliyor: codable tipin şekli değişse (yeni alan eklense, biri çıkarılsa) bile SwiftData bunu bir migration olarak görmüyor ve şema versiyonlaması tetiklemiyor. Sorumluluk tamamen senin Codable implementasyonuna kalıyor — encode/decode'un ileri ve geri uyumlu kalması gerekiyor.

Bu iki sınırlamanın pratikteki karşılığı şöyle özetlenebilir:

  • Filtreleme kaybı: Kullanıcıya "externalLocation'a göre filtrele" gibi bir arama özelliği sunacaksan, bu alanı .codable yerine ayrı, native bir alanda (ör. düz String) da tutman gerekebilir.
  • Sıralama kaybı: Aynı şekilde bir liste ekranında .codable alana göre sıralama istiyorsan, sıralama anahtarını ayrı bir native alanda kopyalaman gerekiyor — SwiftData bunu senin için yapmıyor.
  • Migration serbestliği: Bu kaybın karşılığında kazandığın şey, codable payload'unu SwiftData şema migration sürecinden tamamen bağımsız evrimleştirebilmen; yeni alan eklemek için VersionedSchema tanımlamana gerek kalmıyor.
  • Escape hatch disiplini: Apple'ın "kaçış kapağı" ifadesini ciddiye almak, .codable'ı yalnızca gerçekten sahibi olmadığın tipler için kullanmak ve kendi domain modellerini native @Model alanlarıyla tanımlamaya devam etmek anlamına geliyor.
swift
1// externalLocation'ın şekli değişse bile SwiftData migration tetiklemez;
2// forward/backward compatibility'yi Codable implementasyonun garanti etmeli.
3struct LegacyLocationPayload: Codable {
4 var identifier: String
5 var displayName: String? // sonradan eklenen alan — decode'da opsiyonel kalmalı
6}
Özellik
Native @Model alanı
.codable attribute
#Predicate ile filtrelenebilir mi
Evet
Hayır
SortDescriptor ile sıralanabilir mi
Evet
Hayır
Şekil değişince migration tetikler mi
Evet
Hayır
Sahibi olmadığın (3. parti) tip saklayabilir mi
Hayır (genelde)
Evet

ResultsObserver: SwiftUI Dışında Canlı Sorgu İzleme

@Query her zaman bir SwiftUI view'a bağlıydı — view dışında (bir servis katmanında, bir motor sınıfında) aynı canlı gözlemi kurmanın resmi bir yolu yoktu. iOS 27'de gelen ResultsObserver final class'ı bu boşluğu dolduruyor: SwiftUI'a bağımlı olmadan, Swift Observation üzerinden belirttiğin fetch kriterlerine uyan modellerdeki değişiklikleri gerçek zamanlı izliyor.

ResultsObserver, @Query ile aynı primitifi paylaşıyor: sectionsız kullanımda SectionTitle tip parametresi olarak Never veriyorsun, sectionlı kullanımda ise sectionBy ile birlikte somut bir tip (ör. String) veriyorsun. Değişiklikleri dinlemek için didSet option'ı ile withContinuousObservation kullanıyorsun; bu fonksiyon her değişiklikte callback tetikliyor ve geriye, gözlemin ömrünü kontrol etmen için bir ObservationTracking.Token döndürüyor.

swift
1import Observation
2import SwiftData
3 
4@MainActor
5final class MapCameraController {
6 private var observationToken: ObservationTracking.Token?
7 private let observer: ResultsObserver<Trip, Never>
8 
9 init(context: ModelContext) throws {
10 observer = try ResultsObserver<Trip, Never>(modelContext: context)
11 observationToken = withContinuousObservation(options: [.didSet]) { [weak self] _ in
12 // SwiftUI dışında, ör. bir kamera denetleyicisinde
13 // güncel Trip listesine göre kamerayı yeniden konumlandır
14 _ = self?.observer.results
15 }
16 }
17}

Bu tasarım, ResultsObserver'ın yalnızca "SwiftUI'sız @Query" olmadığını, aynı zamanda sectioning primitifini de paylaştığını gösteriyor — sectioned bir ResultsObserver kurup .sections üzerinden aynı gruplu sonuçlara SwiftUI dışında da erişebiliyorsun.

ResultsObserver'ın pratikte açtığı kapılar şunlar:

  • Servis katmanı entegrasyonu: Bir senkronizasyon servisinin veya arka plan görevinin, kullanıcı arayüzünden bağımsız olarak güncel veri kümesini izlemesi artık resmi bir API ile mümkün.
  • Test edilebilirlik: ResultsObserver, SwiftUI view hiyerarşisine ihtiyaç duymadığı için unit test içinde de kurulabiliyor — bir view'ı render etmeden sorgu gözlemleme mantığını doğrulayabiliyorsun.
  • Aynı fetch mantığının tekrar kullanımı: @Query ile SwiftUI'da, ResultsObserver ile SwiftUI dışında aynı FetchDescriptor ve sectionBy mantığını paylaştığın için filtreleme kuralını iki yerde ayrı ayrı yazmıyorsun.

HistoryObserver: Persistent History ve Uzak Değişiklikler

ResultsObserver belirli bir fetch kriterine uyan sonuçları izlerken, HistoryObserver daha geniş bir soruna cevap veriyor: container'ın veri depolarında (ör. CloudKit sync'ten veya başka bir process'ten) olan uzak değişiklikleri nasıl fark edeceksin? HistoryObserver, ModelContainer'ın remoteChange bildirimlerini otomatik dinleyen ve gelen değişikliklerin ilgili olup olmadığını belirleyen ayrı, yeni bir sınıf.

İlgili bir değişiklik algılandığında, observer kendi @Observable eventCounter property'sini artırıyor; bir SwiftUI view veya başka bir observer bu eventCounter'ı izleyerek tepki verebiliyor. Performans tarafında HistoryObserver, her veri deposundaki transaction geçmişindeki konumunu historyTokens ile takip ediyor — böylece yalnızca son kontrolden bu yana eklenen yeni transaction'lar artımlı olarak işleniyor. observedModels parametresiyle de gözlemi belirli model tiplerine daraltabiliyorsun.

swift
1import SwiftData
2 
3final class SyncStatusStore {
4 private let historyObserver: HistoryObserver
5 
6 init(container: ModelContainer) throws {
7 historyObserver = try HistoryObserver(
8 observedModels: [Trip.self],
9 modelContainer: container
10 )
11 }
12 
13 var hasRemoteChanges: Bool {
14 historyObserver.eventCounter > 0
15 }
16}

ResultsObserver "hangi sonuçlar değişti" sorusuna, HistoryObserver ise "container'da herhangi bir yerde bir şey değişti mi" sorusuna cevap veriyor — ikisi birbirini tamamlayan, farklı seviyelerde çalışan API'ler.

HistoryObserver'ı kullanırken aklında tutman gereken noktalar:

  • historyTokens artımlı çalışır: Her kontrolde tüm geçmişi baştan taramıyor, yalnızca son kontrolden bu yana eklenen transaction'ları işliyor — bu, sık aralıklarla kontrol etsen bile maliyeti düşük tutuyor.
  • observedModels daraltması performansı etkiler: Container'daki her model tipini izlemek yerine yalnızca gerçekten ilgilendiğin tipleri (ör. senkronize olması gereken modeller) belirtmek, gereksiz callback tetiklenmesini önlüyor.
  • CloudKit sync senaryosunda merkezi rol oynuyor: Uzak process'ten (başka bir cihazdan CloudKit sync ile) gelen değişiklikleri fark etmenin resmi yolu HistoryObserver — önceden bunun için NSPersistentHistoryTracking'e benzer elle kurulan mekanizmalar gerekiyordu.

Production Lessons'taki Hangi Kısıtlar Kapandı?

SwiftData'yla 6 ayı geride bıraktığımda yazdığım production lessons yazısında Core Data'ya geri döndüğümüz üç senaryoyu anlatmıştım; iOS 27'nin bu dört eklemesi bunlardan yalnızca birini gerçekten kapatıyor. Senaryo 3'te cross-device offline-first bir uygulamada NSPersistentHistoryToken ile sync state takip ediyorduk ve o yazıdaki ifademle SwiftData tarafında "özellikle fetchHistory(after: token) denkliği yok. Manuel implement etmek 200+ satır" idi. HistoryObserver, ModelContext'in fetchHistory metoduyla birlikte tam olarak bu elle yazılan katmanın yerini alıyor. Senaryo 1 (1,5M satır + compound predicate'te %25-40 yavaşlık) ve Senaryo 2 (custom NSManagedObject davranışı, validation hook'ları) ise açık kalmaya devam ediyor — sectioning, .codable ve ResultsObserver bu iki senaryoya dokunmuyor.

Bununla birlikte iOS 27'nin kapatmadığı bir kısıt da var: performans karakteristiği. SwiftData vs Core Data performans karşılaştırmasında ölçtüğüm farklar bu dört ekleme ile değişmiyor — sectioning, .codable, ResultsObserver ve HistoryObserver hepsi API yüzeyi ve geliştirici deneyimi eklemeleri; sorgu motorunun kendisini değiştirmiyorlar. Dolayısıyla performans kararını verirken o yazıdaki ölçümlere bakmaya devam edebilirsin; bu yazı yalnızca "ne yeni yapabiliyorsun" sorusuna cevap veriyor.

Genel SwiftData temel bilgisi için SwiftData'ya kapsamlı giriş yazımıza da bakabilirsin — sectioned query ve .codable, o yazıdaki temel @Model/@Query kavramlarının üzerine ekleniyor.

Migration ve Geriye Dönük Uyumluluk

.codable attribute'ların migration davranışı yukarıda net: şekil değişse bile migration tetiklenmiyor, uyumluluk sorumluluğu senin Codable implementasyonunda. Sectioned query tarafında ise sectionBy, fetch tarafında bir gruplama parametresi olduğu için şemayı değiştirmiyor; Apple sectionBy eklemenin veya çıkarmanın migration'a etkisine dair ayrıca bir açıklama yapmıyor. Var olan bir Codable modelini genişletirken en güvenli yol, yeni alanları her zaman opsiyonel (Optional) tanımlamak ve decode'da eksik alan durumunu ele almak.

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

Bu yazıyı üretim koduna taşımadan önce kontrol etmen gereken maddeleri tek bir checklist'te topladım. Sectioned query, .codable ve ResultsObserver'ı aynı anda bir projeye eklerken atlanması en kolay adımlar burada; her birini işaretleyerek ilerleyebilirsin.

SSS

SwiftData sectioned query nedir, nasıl kullanılır?

iOS/iPadOS 27 ile @Query'ye eklenen sectionBy parametresi, model kök tipinden başlayan bir KeyPath alır ve sonuçları bu değere göre gruplar. Property wrapper'ın alt çizgili adıyla (_trips) erişilen .sections koleksiyonu üzerinde ForEach ile hem section hem section içi elemanlar üzerinde dolaşırsın; her section'ın id'si sectionBy KeyPath'inin değeridir.

SwiftData'da özel tipleri nasıl saklarım (.codable)?

Sahibi olmadığın, Codable uyumlu bir tipi (ör. bir framework'ün tanımladığı tip) saklamak için model özelliğine @Attribute(.codable) ekliyorsun. SwiftData serileştirmeyi tipin kendi Codable implementasyonuna devrediyor; karşılığında bu alan #Predicate'te filtrelenemiyor ve SortDescriptor ile sıralanamıyor, ayrıca şekli değişse bile migration tetiklemiyor.

ResultsObserver SwiftUI dışında nasıl kullanılır?

ResultsObserver, @Query'nin SwiftUI dışı karşılığı: aynı filtreleme/sıralama/sectioning primitiflerini destekler ama Swift Observation üzerinden herhangi bir kod yolunda çalışır. didSet option'ı ile withContinuousObservation çağırarak değişiklik callback'i alır, dönen ObservationTracking.Token değerini gözlemin ömrü boyunca saklarsın.

iOS 27 SwiftData, Core Data'ya dönme kararını değiştirir mi?

Hayır. iOS 27'deki eklemeler (sectioning, .codable, ResultsObserver, HistoryObserver) gerçek ama noktasal iyileştirmeler; aynı döngüde Core Data'da hiçbir yenilik yok ve object-ID interop hâlâ eksik. Bu yüzden iOS 27'yi SwiftData'yı "artık kesin production-ready" ilan eden bir sürüm olarak değil, önceki sınırlamaların bir kısmını kapatan kademeli bir adım olarak okumak daha doğru.

Güncelleme (Eylül 2026)

Apple'ın iOS & iPadOS 27 Release Notes ve macOS 27 Golden Gate Release Notes sayfalarının SwiftData bölümünde şu madde yer alıyor: bir background actor üzerinde ModelContext kaydederken aynı anda ModelActor için yeni async görevler zamanlanıyorsa @Query tarafında oluşabilen bir deadlock düzeltildi (bug 178113288). Bu, bu yazıda anlattığım sectioned query, .codable, ResultsObserver ve HistoryObserver API yüzeyinde herhangi bir değişiklik değil — bir eşzamanlılık hatasının giderilmesi.

Sonuç

iOS 27'nin SwiftData'ya getirdiği dört ekleme — sectioned query, .codable, ResultsObserver, HistoryObserver — ortak bir tema paylaşıyor: SwiftData'yı gerçek uygulamalarda kullanırken elle yazman gereken workaround'ları resmi API'ye taşımak. Sectioned query'yi ve .codableSwiftData'ya kapsamlı giriş yazımızdaki temel @Model/@Query bilgisinin üzerine ekleyebilirsin; performans kararı verirken SwiftData vs Core Data karşılaştırmamıza bakmaya devam et, çünkü bu dört ekleme sorgu motorunun performans karakteristiğini değiştirmiyor. Production lessons yazımızda anlattığım kısıtların bir kısmı bu eklemelerle hafifliyor; hepsi kapanmıyor.

Bu yazı beta dönemindeki (Haziran 2026) API yüzeyini anlatıyor; GA sonrası davranış değişikliği görürsen bu yazının Güncelleme bölümüne bakmayı unutma.

Kaynaklar

Etiketler

#SwiftData#iOS 27#WWDC26#Sectioned Query#Codable#ResultsObserver#Swift
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

12+ yıllık deneyime sahip Lead Mobile Engineer. Swift, SwiftUI, Kotlin ve Flutter ile iOS, Android ve cross-platform mimarilerde uzman. Performanslı ve kullanıcı dostu mobil 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