Swift 6 strict concurrency, derleyicinin tüm veri yarışlarını derleme zamanında tespit edip hata olarak işaretlediği bir veri yarışı güvenliği modudur. Pratik olarak bu, paylaşılan mutable durumun yalnızca Sendable tipler aracılığıyla aktarılması, izole olmayan kapama bloklarının yasaklanması ve aktör sınırlarının derleyici tarafından zorlanması anlamına gelir. Bu rehberde Swift 6.2 (Xcode 26.3) ile birlikte gelen Sendable protokolünü, aktör izolasyonunu, bölge tabanlı izolasyonu ve mevcut bir projeyi complete concurrency moduna geçirmenin gerçek adımlarını anlatacağım. Geçen ay 120 bin satırlık bir bankacılık uygulamasını Swift 6'ya taşırken edindiğim notlar da burada.
Swift 6'da SWIFT_STRICT_CONCURRENCY=complete varsayılan olarak açıktır; uyarılar artık derleme hatasına dönüşür.
Sendable protokolü, bir değerin izolasyon sınırları arasında güvenle aktarılabileceğini derleyiciye bildiren bir işaret protokolüdür.
Region-based isolation (SE-0414) sayesinde Swift 6.2'de let olmayan bazı değerler bile artık aktarılabilir (transferring).
@MainActor, actor ve nonisolated kombinasyonu, UI ile arka plan iş yüklerini ayırmanın temel aracıdır.
@unchecked Sendable kullanımı son çare olmalıdır. Geliştirici, eşzamanlılık güvenliğinden manuel olarak sorumlu hale gelir.
Geçişi modül bazında incremental yapın: önce minimal, sonra targeted, en son complete.
Swift 6 strict concurrency nedir?
Strict concurrency, Swift derleyicisinin tüm potansiyel veri yarışlarını (data race) derleme zamanında saptayıp programın çalışmasını engellediği güvenlik modudur. Swift 5.x döneminde bu kontroller varsayılan olarak kapalıydı veya yalnızca uyarı üretiyordu. Swift 6 ile birlikte complete seviye standart hâle geldi ve uyarılar artık derleme hatası olarak rapor edilir. Apple bu adımı, modern paralel iOS, macOS ve visionOS uygulamalarında en yaygın crash kaynağı olan eşzamansız hataları ortadan kaldırmak için attı.
Pratik bir örnekle anlatalım. Aşağıdaki kod Swift 5'te uyarısız derlenirken, Swift 6'da hata verir çünkü counter birden fazla görev arasında izolasyonsuz olarak paylaşılıyor:
// Swift 6'da derleme hatası: "Mutation of captured var 'counter' in concurrently-executing code"
var counter = 0
await withTaskGroup(of: Void.self) { group in
for _ in 0..<100 {
group.addTask {
counter += 1 // veri yarışı
}
}
}
Bu kontroller derleyici düzeyinde yapıldığı için runtime maliyeti yoktur. Yalnızca yazım anında daha fazla disiplin gerektirir; karşılığında uygulamanız klasik thread-safety bug'larından tamamen arınmış olur. Bu yaklaşım Swift resmi geçiş rehberinde ayrıntılı biçimde belgelenmiştir.
Sendable protokolü ve uyumluluk kuralları
Sendable, bir tipin değerlerinin eşzamanlılık sınırları (görevler, aktörler, izolasyon bölgeleri) arasında güvenle aktarılabileceğini ifade eden bir marker protocol'dür. İçinde hiçbir gereksinim yoktur; derleyici, tipin yapısına bakarak uygunluğu otomatik olarak doğrular veya reddeder. Anlamsal kural şudur: bir Sendable değerini bir görevden başka bir göreve aktardığınızda, iki taraf da onu güvenle okuyup yazabilmelidir.
Aşağıdaki tablo, hangi tiplerin otomatik olarak Sendable sayıldığını özetler:
Tip kategorisi
Otomatik Sendable mi?
Koşul
Değer tipleri (struct, enum)
Evet
Tüm depolanan üyeleri Sendable ise
final class
Koşullu
Tüm üyeleri immutable (let) ve Sendable ise
Aktör (actor)
Evet
Her zaman; izolasyon derleyici tarafından garanti edilir
Fonksiyon/closure
Hayır (varsayılan)
@Sendable imzasıyla işaretlenmeli
Sınıf hiyerarşileri (non-final)
Hayır
Yalnızca @unchecked Sendable ile manuel onay
Generic tipler
Koşullu
Tip parametreleri Sendable ise (conditional conformance)
Pratik bir örnek. Basit bir DTO doğal olarak Sendable'dır:
struct UserProfile: Sendable {
let id: UUID
let displayName: String
let createdAt: Date
}
// Doğrudan Task'lara aktarılabilir
Task {
let profile = UserProfile(id: UUID(), displayName: "Ayşe", createdAt: .now)
await uploadProfile(profile) // güvenli
}
Aktör izolasyonu ve @MainActor
Aktörler (actor), mutable durumu kapsülleyip ona erişimi seri hâle getiren referans tipleridir. Bir aktörün üyelerine dışarıdan erişim her zaman await ile yapılır ve derleyici, aynı anda yalnızca bir görevin aktörün durumunu değiştirebilmesini garanti eder. Bu, kilitler (lock) veya kuyruklarla manuel senkronizasyona alternatif, derleyici tarafından doğrulanan bir modeldir.
actor ImageCache {
private var storage: [URL: Data] = [:]
func image(for url: URL) -> Data? {
storage[url]
}
func insert(_ data: Data, for url: URL) {
storage[url] = data
}
}
// Kullanım
let cache = ImageCache()
let data = await cache.image(for: url) // await zorunlu
@MainActor, özel ve tek bir global aktör'dür: kendisiyle işaretlenmiş tüm kod, ana iş parçacığında (main thread) çalışır. SwiftUI View tipleri ve UIKit UIViewController alt sınıfları Swift 6'da varsayılan olarak @MainActor'a izole edilmiştir. Arka planda çalışması gereken bir metodu tek tek dışarı almak için nonisolated kullanırsınız:
@MainActor
final class FeedViewModel: ObservableObject {
@Published var items: [FeedItem] = []
nonisolated func computeChecksum(of data: Data) -> String {
// CPU yoğun, UI ile alakasız: arka planda çalışmasına izin ver
SHA256.hash(data: data).description
}
func reload() async {
let fresh = try? await FeedService.shared.fetch()
items = fresh ?? [] // ana iş parçacığında
}
}
SwiftUI tabanlı bir uygulamanın modern veri katmanını kurmak istiyorsanız, SwiftData ile @Model ve @Query kullanım rehberimiz aktör izolasyonu ile birlikte kullanılması gereken kalıcılık kalıplarını detaylandırır.
Region-based isolation (SE-0414)
Region-based isolation, Swift 6'nın en az anlaşılan ama en güçlü özelliğidir. Geleneksel kurala göre Sendable olmayan bir değer asla görev sınırlarını geçemezdi. SE-0414 önerisi ile derleyici artık değişkenlerin izolasyon bölgelerini analiz eder: bir nesne yalnızca tek bir referansla tutuluyorsa (kimse paylaşmıyorsa), Sendable olmasa bile başka bir aktöre veya göreve aktarılabilir (transferring).
// NSImage Sendable değil, ama burada izole bir bölgede üretildi
func processImage(at url: URL) async throws {
let image = NSImage(contentsOf: url)! // izole bölge
// 'image' artık başka kimse tarafından tutulmuyor → aktarılabilir
try await ImageProcessor.shared.process(image) // Swift 6.2'de geçerli
}
Bu özellik, Sendable olmayan eski Foundation/AppKit tipleriyle çalışırken yaşanan ağrıyı büyük ölçüde azaltır. Derleyicinin "bu değer izole bir bölgede" çıkarımını yapabilmesi için değişkene başka referans tutmamanız gerekir. Değer aktarıldıktan sonra yerel kapsamda kullanmaya çalışırsanız hata alırsınız.
Sendable uyarılarını nasıl çözerim?
Sendable uyarıları ve hataları beş ana kategoriye düşer ve her birinin doğru çözüm yolu farklıdır. Yanlış çözüm, örneğin her şeye @unchecked Sendable yapıştırmak, yarışları gizler, çözmez. Aşağıda sık karşılaşılan durumlar ve önerilen çözümler yer alıyor.
1. "Capture of 'self' with non-sendable type in @Sendable closure"
Bir görev (Task) içinde bir sınıfın self'ini yakaladığınızda görülür. Sınıfı bir aktöre dönüştürün veya @MainActor'a izole edin:
@MainActor
final class DownloadManager { // çözüm: izole et
func start() {
Task {
await self.fetch() // artık güvenli
}
}
}
2. "Type 'X' does not conform to the 'Sendable' protocol"
Sendable bir bağlama, Sendable olmayan bir değer geçirdiğinizde olur. Tip bir struct ise alanları kontrol edin; büyük olasılıkla içinde Sendable olmayan bir sınıf vardır. Mümkünse o sınıfı da değer tipine çevirin veya immutable bir final class yapın.
Statik var kullanımları Swift 6'da tehlikeli kabul edilir. Çözüm: ya let yapın ya da bir aktörün arkasına yerleştirin.
5. NotificationCenter/observer kapama blokları
Observer closure'ları @Sendable olmak zorundadır. self'i [weak self] ile yakalayın ve içeride Task { @MainActor in ... } kullanın. Bu kalıbı bir push notification handler'da kaçırdığım için bir gece boyunca crash log okuduğumu hatırlıyorum, dikkatli olun.
@unchecked Sendable ne zaman kullanılır?
@unchecked Sendable, derleyiciye "ben bu tipin eşzamanlılık güvenliğini manuel olarak garanti ediyorum, kontrol etme" demenizi sağlar. Adından da anlaşılacağı üzere bu bir kaçış yoludur ve sorumluluğu sizden almaz; aksine size yükler. Yanlış kullanılan @unchecked Sendable, tam olarak Swift 6'nın engellemek için tasarlandığı veri yarışlarına yol açar.
Meşru kullanım senaryoları sınırlıdır:
Kendi kilidini yöneten tipler: İçinde NSLock, os_unfair_lock veya benzeri ile senkronizasyon yapan eski sınıflar.
Kopyalanmayan veri tutucu sınıflar (CoW): Yapı (struct) gibi davranan ama performans nedeniyle sınıf olarak yazılan tipler.
Üçüncü taraf Sendable olmayan tipleri sarmalayan adaptörler: Apple framework'lerinin henüz güncellenmemiş tiplerini izole bağlamlarda kullanmak.
final class ThreadSafeCounter: @unchecked Sendable {
private let lock = NSLock()
private var _value = 0
var value: Int {
lock.lock(); defer { lock.unlock() }
return _value
}
func increment() {
lock.lock(); defer { lock.unlock() }
_value += 1
}
}
Swift 6 strict concurrency'ye geçiş adımları
Mevcut bir Swift 5 projesini Swift 6'ya geçirmek, hepsini bir gecede yapmaya çalışmak yerine kademeli yapıldığında çok daha az sancılıdır. Apple'ın resmi Swift 6 geçiş dökümantasyonu da modül başına ilerlemeyi önerir. Aşağıdaki sıralama, gerçek bir orta-büyük iOS projesinde uyguladığım çalışma akışıdır.
Adım 1: Swift 5 dilinde, strict concurrency'yi "minimal" açın
Xcode'da hedefin Build Settings → Swift Compiler - Upcoming Features altında SWIFT_STRICT_CONCURRENCY=minimal ayarlayın. Bu yalnızca açıkça Sendable işaretlenmiş tipleri kontrol eder. Çıkan uyarıları sıfıra indirin.
Adım 2: "targeted" seviyeye yükseltin
Bu seviye, Swift 6 ile uyumlu olarak imzalanmış API'lerde Sendable kontrolünü zorlar. Bu noktada framework'leri günceller ve kendi kodunuzun aktör izolasyonunu netleştirirsiniz.
Adım 3: Modül modülünde "complete"'a geçin
Tek bir hedefte SWIFT_STRICT_CONCURRENCY=complete deneyin. Hata sayısı yüzlerle ölçülebilir; panik yapmayın. Çoğu hata kalıbı tekrar eder; bir tipi izole edince ona bağımlı 30 hata birden çözülür. (Benim ilk denemem 412 hatayla başladı, ikinci günün sonunda 60'a düşmüştü.)
Adım 4: Swift Language Mode'u 6'ya alın
Tüm hedefler complete modda derlendiğinde SWIFT_VERSION=6 yapın. Bu noktada uyarılar derleme hatasına dönüşür ve geri dönüş yolu kapanır. Ama o kapı bir kere kapandığında, projenizde artık veri yarışı yoktur.
Geçiş sürecinde Xcode'un AI destekli düzeltme önerilerinden faydalanmak isterseniz Xcode 26.3 ajan tabanlı kodlama rehberimizdeki iş akışları, tekrarlayan Sendable düzeltmelerini önemli ölçüde hızlandırır.
Sık karşılaşılan hatalar ve çözümleri
Aşağıda strict concurrency geçişinde en çok karşılaştığım dört hata kalıbı ve önerilen düzeltmeler yer alıyor. Bu hatalar genellikle Swift 5'te sessizce çalışan koddan kaynaklanır; yani bug yoktur sanırsınız ama derleyici size doğru olanı söyler.
"Sending value of non-Sendable type"
Bir görev sınırı geçerken Sendable olmayan bir değer aktarıyorsunuz. İki yoldan biri: ya değeri Sendable yapın, ya da region-based isolation'a uygun şekilde yeniden yapılandırın (değerin başka referans tarafından tutulmadığından emin olun).
"Main actor-isolated property cannot be referenced from a non-isolated context"
Arka planda çalışan bir kapanış içinde MainActor'a izole bir özelliğe erişiyorsunuz. await MainActor.run { ... } bloğu ekleyin veya çağıran fonksiyonu @MainActor ile işaretleyin.
"Call to main actor-isolated initializer in a synchronous nonisolated context"
SwiftUI View initializer'larında oluşabilir. init'i @MainActor yapın veya ilgili tipi nonisolated init ile genişletin.
Singleton'lar ve global state
Klasik static let shared = MyService() kalıbı, MyService Sendable değilse derlemez. Çözüm: MyService'i bir actor'e dönüştürün veya @MainActor'a izole edin. Tüm singleton'ları aynı anda dönüştürmeye çalışmayın; kullanım sıklığına göre öncelik verin.
Sıkça Sorulan Sorular
Swift 6 strict concurrency'yi devre dışı bırakabilir miyim?
Evet, hedef bazında SWIFT_STRICT_CONCURRENCY=minimal ayarlayıp SWIFT_VERSION=5'te kalarak geçişi erteleyebilirsiniz, ancak iOS 18+ hedefleyen yeni projeler için bunu önermiyorum. Geçiş ne kadar geciktirilirse ileride yapılacak iş o kadar büyür.
actor ve @MainActor arasındaki fark nedir?
actor, kendi serileştirilmiş izolasyon bağlamına sahip yeni bir tip yaratır; her aktör örneği bağımsızdır. @MainActor ise tek ve global bir aktördür. Onunla işaretlenen her şey aynı paylaşılan ana iş parçacığında çalışır. UI kodu için @MainActor, kapsüllenmiş arka plan durumu için actor kullanın.
@unchecked Sendable kullanmak güvenli mi?
Yalnızca tipin thread-safety'sini manuel olarak (kilitler, atomik işlemler veya immutability ile) sağladığınızdan kesin eminseniz güvenlidir. Aksi takdirde, derleyicinin yakalamayı bıraktığı veri yarışlarını koda dahil etmiş olursunuz. Her kullanımı yorum satırıyla belgeleyin.
Swift 6 geçişi ne kadar sürer?
Orta büyüklükte (50 ila 150 bin satır) bir iOS projesinde tipik geçiş süresi, eşlik eden refactor yapılmadan 2 ila 4 hafta arasındadır. Asıl zaman alıcı kısım kodlama değil; doğru izolasyon stratejisine karar vermektir. Eski kilit tabanlı kodu doğrudan aktörlere çevirmek genellikle en temiz sonucu verir.
Region-based isolation hangi Xcode sürümünde geldi?
SE-0414 önerisi Swift 6.0 ile birlikte geldi ve Xcode 16.0'dan itibaren mevcuttur. transferring parametre anahtar sözcüğü gibi ilgili ergonomik iyileştirmeler Swift 6.2 / Xcode 26.3 ile tamamlanmıştır. Daha eski Xcode sürümlerinde aynı kalıpları yazmak çok daha fazla @unchecked Sendable gerektirir.
SwiftUI NavigationStack ve NavigationPath ile tip güvenli navigasyon: Router deseni, derin bağlantı işleme, cold-launch durum geri yükleme ve VoiceOver erişilebilirliği için 2026 rehberi.
Swift Testing, XCTest'in yerini alan modern framework. @Test makrosu, #expect kontrolleri, paralel çalıştırma ve @Suite ile Swift 6 ve Xcode 16'da birim testlerin nasıl yazıldığını, XCTest'ten kademeli geçiş örnekleriyle görün.