# Swift 6.2 ve WWDC26 Sonrası: Concurrency Artık Gerçekten "Approachable" mı?
Swift 6.0'ın strict concurrency modu, data-race güvenliğini derleyici seviyesinde garanti etti — ama bedeli ağırdı. Var olan 60K+ satırlık projelerde Sendable hataları duvar gibi karşınıza çıktı, @MainActor her yere serpiştirilmek zorunda kaldı. WWDC25'te duyurulan Approachable Concurrency girişimi ve Swift 6.2 ile Apple bu problemi doğrudan hedef aldı: "Swift, sadece kullandığınız kadar concurrency anlamanızı istemeli" — progressive disclosure felsefesi.
Bu yazıda üç somut değişikliği (default actor isolation, @concurrent, sezgisel async fonksiyonlar) ve WWDC26'da Swift 6.4 ile gelen ek iyileştirmeleri, gerçek production migration perspektifinden inceliyorum.
Pro Tip: Xcode 26 ile açılan yeni proje ve hedeflerde Approachable Concurrency ve MainActor default isolation otomatik açık geliyor. Mevcut (var olan) hedefleriniz için bu opt-in — build setting veya Package.swift üzerinden elle açmanız gerekiyor.İçindekiler
- SE-0466: Main Actor by Default
- SE-0461: Sezgisel Async Fonksiyonlar —
nonisolated(nonsending) @concurrent: Paralellik İçin Açık Niyet Attribute'u- Production Migration: Swift 6.0 → 6.2 Geçişinde Ne Değişiyor
- WWDC26 Sonrası: Swift 6.4'te Concurrency
- Migration Checklist
1. SE-0466: Main Actor by Default
Swift 6.2'den önce, izole edilmemiş (nonisolated) her tip ve fonksiyon varsayılan olarak hiçbir actor'a bağlı değildi — yani derleyici onu herhangi bir thread'de çalışabilir kabul ediyordu. UI-ağırlıklı bir app'te bu, aslında hep main thread'de çalışan kodun bile gereksiz Sendable uyarıları üretmesine yol açıyordu.
SE-0466 ile gelen -default-isolation MainActor derleyici bayrağı (veya Package.swift manifest ayarı), bir hedefteki tüm izole edilmemiş kodu varsayılan olarak `@MainActor`'a bağlar:
swift
1// Swift 6.0 — explicit @MainActor gerekli2@MainActor3final class ProfileViewModel: ObservableObject {4 @Published var user: User?5 6 func load() async {7 user = try? await UserService.shared.fetchCurrent()8 }9}10 11// Swift 6.2 — default-isolation MainActor açıkken12// @MainActor annotation'a gerek yok, derleyici zaten varsayıyor13final class ProfileViewModel: ObservableObject {14 @Published var user: User?15 16 func load() async {17 user = try? await UserService.shared.fetchCurrent()18 }19}Bu, script'ler, CLI araçları ve UI-ağırlıklı executable hedefler için idealdir — concurrency kullanmayan kodda sahte-pozitif data-race uyarılarını ortadan kaldırır. Ama bir kütüphane (library target) yazıyorsanız bu varsayılanı açmayın: tüketiciniz farklı isolation modellerinde çalışabilir, sizin default MainActor kararınız onlara sızar.
Hedef Türü | Default Isolation Önerisi |
|---|---|
Yeni app/executable (Xcode 26) | MainActor (otomatik açık) |
Mevcut app hedefi | Modül modül, elle aç |
Paylaşılan Swift Package (library) | Nonisolated kalsın, elle karar ver |
Networking/Persistence katmanı | Genelde nonisolated + explicit `@concurrent` |
2. SE-0461: Sezgisel Async Fonksiyonlar — `nonisolated(nonsending)`
Swift 6.0'da nonisolated async bir fonksiyon çağrıldığında, hangi actor'dan çağrılırsa çağrılsın her zaman global concurrent executor'a atlıyordu. Bu, çoğu zaman geliştiricinin niyetiyle çelişiyordu — main actor'da çağrılan bir async fonksiyonun sonuçta yine main actor'a dönmesi gerekiyordu, ama arada gereksiz bir thread hop oluyordu.
SE-0461 ile nonisolated(nonsending) davranışı varsayılan hale geldi: nonisolated async fonksiyonlar artık çağıranın execution context'inde çalışır, global executor'a otomatik atlamaz.
swift
1// Nonisolated async fonksiyon2func validate(_ input: String) async -> Bool {3 // Swift 6.2: çağıranın actor'unda çalışır (ör. @MainActor)4 // Swift 6.0: her zaman global executor'a atlardı5 return !input.isEmpty6}7 8@MainActor9func onSubmit(_ text: String) async {10 let isValid = await validate(text) // artık gereksiz thread hop yok11 submitButton.isEnabled = isValid12}Gerçekten paralel çalışmasını istediğiniz kod için ise açık bir işaretleyiciye ihtiyaç var — bu da bizi @concurrent'e getiriyor.
Bunun test kodu üzerindeki etkisi hafife alınmamalı. Swift 6.0'da mock servisleri genelde Task { await ... } ile sarmalamak zorunda kalıyordunuz, çünkü nonisolated async bir mock fonksiyon çağrısı beklenmedik şekilde arka plan thread'ine düşüyor ve test assertion'ları main actor'daki state ile yarışa giriyordu. SE-0461 sonrası, test main actor'dan çağırıyorsa mock da main actor'da çalışır — flaky test sayısında gözle görülür bir azalma sağlıyor, özellikle @MainActor-izole ViewModel'leri test eden XCTest ve Swift Testing hedeflerinde.
3. `@concurrent`: Paralellik İçin Açık Niyet Attribute'u
@concurrent, "bu kodun bilerek eş zamanlı (global executor'da) çalışmasını istiyorum" demenin resmi yolu. CPU-yoğun iş — büyük JSON decode, görüntü işleme, kriptografik hesaplama — için idealdir:
swift
1@concurrent2func decodeLargePayload(_ data: Data) async throws -> [Record] {3 // Bilinçli olarak concurrent executor'da çalışır,4 // main actor'u bloklamaz5 try JSONDecoder().decode([Record].self, from: data)6}7 8@MainActor9func handleResponse(_ data: Data) async {10 isLoading = true11 defer { isLoading = false }12 records = try? await decodeLargePayload(data)13}Pratik kural: varsayılan davranış artık "serialized kal", istisna "paralel çalış". Swift 6.0'da bu tam tersiydi — her şey potansiyel olarak paralel kabul edilir, siz @MainActor ile serileştirirdiniz. Bu ters çevirme, UI-ağırlıklı kod tabanlarında boilerplate'i belirgin biçimde azaltıyor.
Pro Tip: @concurrent işaretlediğiniz fonksiyon içinde main actor'a izole bir property'e erişmeye çalışırsanız derleyici hata verir — bu iyi bir şey. CPU-yoğun işi UI state'inden fiziksel olarak ayırmaya zorlanmış olursunuz.4. Production Migration: Swift 6.0 → 6.2 Geçişinde Ne Değişiyor
Zaten strict concurrency'ye geçmiş bir production kod tabanınız varsa, Swift 6.2'ye upgrade davranış değiştirmez — default isolation ve approachable concurrency ayarları yeni hedefler için varsayılan, mevcut hedefler için opt-in. Yani mevcut @MainActor annotation'larınız, Sendable conformance'larınız çalışmaya devam eder.
Asıl karar, bu yeni varsayılanları ne zaman açacağınız:
- Networking/Persistence gibi altyapı modülleri:
@concurrentile açıkça işaretleyin, default isolation'ı açmayın — bu katmanlar zaten actor-izole olmamalı. - ViewModel/UI katmanı:
-default-isolation MainActoraçmak boilerplate'i belirgin azaltır, çünkü zaten pratikte hepsi main actor'da yaşıyor. - Paylaşılan Swift Package'lar: Değiştirmeyin — tüketici projenizin isolation modelini siz dikte etmemelisiniz.
- Test hedefleri:
nonisolated(nonsending)davranışı test mock'larındaki gereksizawait Task { }sarmalamalarını sadeleştirir; XCTest/Swift Testing hedeflerinde deneyin, riski düşük.
En sık karşılaşılan tuzak: bir modülde default isolation'ı MainActor yapıp, o modülü arka planda ağır iş yapan bir background servisin import ettiğini fark etmemek. Derleyici sizi @concurrent eklemeye zorlar ama isolation sınırını modül bazında değil, hedef bazında düşünmek gerekiyor — yanlış hedefte açarsanız gereksiz main-actor hop'ları geri gelir.
Önemli bir nüans: Approachable Concurrency, Sendable kontrolünün yerini almıyor, sadece onun etrafındaki günlük sürtünmeyi azaltıyor. Bir tipin thread'ler arası paylaşılabilir olup olmadığı sorusu hâlâ tam güçle sorulmaya devam ediyor — default isolation sadece "bu kod nerede çalışır" sorusunu daha sezgisel cevaplıyor, "bu veri güvenli mi paylaşılıyor" sorusunu değil. Migration sırasında bu ikisini birbirine karıştırıp default isolation'ı Sendable hatalarını gizleyen bir çözüm gibi görmek, ileride tespit edilmesi zor data-race'lere kapı aralayabilir.
5. WWDC26 Sonrası: Swift 6.4'te Concurrency
WWDC26'da duyurulan Swift 6.4, approachable concurrency temelinin üzerine pratik, gündelik sürtünmeyi azaltan ekler getirdi:
Öneri | Ne Yapıyor |
|---|---|
**SE-0493** — async `defer` | `defer` bloklarında artık `await` çağrısı yapılabiliyor; async cleanup kodu implicit olarak beklenir |
**SE-0504** — Task Cancellation Shields | İptal edilmiş bir task içinde bile kritik temizlik kodunun (`rollback`, `close`) çalışmasını garanti eder |
**SE-0520** — Throwing Task uyarıları | Bir `Task`'in fırlattığı hata sessizce yutulursa derleyici artık uyarı veriyor |
**SE-0530** — Async `Result` desteği | `await Result { try await ... }` ile async işlemleri `Result` tipine sarmalama |
**SE-0481** — `weak let` (Swift 6.3) | `@unchecked Sendable` gerektirmeden immutable zayıf referanslar |
**SE-0518** — `~Sendable` | Bir tipin **bilinçli olarak** Sendable olmadığını açıkça belirtme |
swift
1// SE-0493: async defer2func importDataset() async throws {3 let importer = try DatasetImporter()4 defer {5 await importer.close() // Swift 6.4 öncesi derleme hatası verirdi6 }7 try await importer.run()8}9 10// SE-0504: Task Cancellation Shield11func commitTransaction() async {12 await withTaskCancellationShield {13 await transaction.rollbackIfNeeded()14 }15}Bu eklerin ortak paydası: Swift 6.0'ın "her şeyi güvenli yap" hedefinden, Swift 6.2 ve 6.4'ün "güvenli olanı kolay yap" hedefine geçiş. Migration açısından SE-0493 özellikle değerli — eski callback-tabanlı cleanup kodlarını async/await'e taşırken defer bloklarında takılan projeler artık serbest.
SE-0520'nin throwing task uyarısı da production'da hafife alınmaması gereken bir düzeltme. Task { try await sync() } gibi bir çağrı Swift 6.0-6.2'de derleyici açısından tamamen sessizdi — hata fırlatılsa bile hiçbir uyarı almadan kaybolabiliyordu. Bu, arka planda başlatılan senkronizasyon veya analytics gönderim task'larında özellikle sinsi bir hata sınıfıydı: crash raporlarına düşmez, log'a yazılmaz, sadece sessizce başarısız olurdu. SE-0530'un Result desteği ise bu tarz task'ları do/catch yazmadan test edilebilir hale getiriyor — özellikle unit test'lerde beklenen hata durumlarını assert etmek eskisinden daha az boilerplate gerektiriyor.
SE-0481 (weak let) ve SE-0518 (~Sendable) daha küçük ama gündelik etkisi yüksek eklemeler. Delegate pattern'lerde weak var kullanmak zorunda kaldığınız için @unchecked Sendable'a düşen sınıflar artık weak let ile gerçek Sendable conformance kazanabiliyor — bu, @unchecked kullanan legacy kod tabanlarını temizlemek için somut bir migration hedefi.
6. Migration Checklist
- Mevcut hedeflerinizde strict concurrency seviyesini (`minimal`/`targeted`/`complete`) doğrulayın — 6.2 upgrade bunu değiştirmez.
- UI/ViewModel katmanı için `-default-isolation MainActor`'ı modül bazında pilot olarak açın, derleyici uyarılarını izleyin.
- CPU-yoğun fonksiyonları (`JSONDecoder`, image processing, crypto) `@concurrent` ile açıkça işaretleyin.
- Test hedeflerinde `nonisolated(nonsending)` sayesinde kaldırılabilecek gereksiz `Task { }` sarmalamalarını temizleyin.
- Swift 6.4'e geçtiyseniz, sessizce yutulan `Task` hatalarını SE-0520 uyarılarıyla tarayın — genelde production'da gizli bug'lardır.
- Kütüphane/paket hedeflerinde default isolation'a **dokunmayın**, tüketici projeye karar bırakın.
Swift 6.2 ve devamındaki 6.4, strict concurrency'nin güvenlik garantisinden ödün vermeden, günlük yazım deneyimini gerçekten sadeleştirdi. Ama "approachable" olması, "otomatik doğru" olduğu anlamına gelmiyor — default isolation'ı doğru hedefte açmak hâlâ mimari bir karar, ve yanlış katmanda açıldığında eski performans sorunlarını (gereksiz main-actor serileşmesi) geri getirebiliyor.
Kaynaklar: Swift.org resmi Swift 6.2 duyurusu, Swift Evolution SE-0461/SE-0466/SE-0493/SE-0504/SE-0520/SE-0530/SE-0481/SE-0518 önerileri, WWDC25 "What's new in Swift" ve WWDC26 "What's new in Swift" oturumları.

