Swift 6 geldi ve en büyük değişiklik strict concurrency checking. Compiler artık thread-safety problem’lerini compile time’da yakalıyor. Xcode 16 ile Swift 6 mode default olduğunda eski kod tabanları ciddi warning/error seli alacak.
2 iOS uygulamamı Swift 6’ya migrate ettim. Toplam 60K LOC civarı. Süreç kolay değildi ama öğrendiklerim var. Bu yazıda migration approach’ımı paylaşacağım.
Swift 6’nın getirdikleri
En büyük değişiklikler:
- Compile-time data race detection. İki thread aynı anda aynı data’ya erişiyor mu? Compiler uyarı veriyor.
- Sendable enforcement. Cross-actor geçen data’nın
Sendableolması zorunlu. Thread-safe olduğunu garanti ediyor. - Strict actor isolation.
@MainActor,@globalActorannotation’ları zorunlu oldu. - Complete concurrency checking. Swift 5.5’teki
-strict-concurrencyflag’i default.
Sonuç: artık “çalışır gibi görünen” concurrent code yazamıyorsun. Compiler sorguluyor.
Pre-migration: state assessment
Migration’a başlamadan önce durumu değerlendir:
1. Mevcut Swift version: 5.9+ ise migration kolay. 5.5 altındaysa önce ara version’a upgrade.
2. Async/await adoption: Projede ne kadar async/await kullanılıyor? Fazlaysa migration kolay. Callback-heavy code base zorlu.
3. Third-party SDK’lar: Hepsi Swift 6-ready mi? Bazıları hâlâ güncel değil. Build uyumsuzluğu riski.
4. Test coverage: Migration refactoring. Test olmadan regression risk çok yüksek.
Bu 4 faktöre göre migration süresi tahmin et. Dentii için:
– Swift 5.9
– Async/await %70 adopted
– SDK’ların %90’ı ready
– Test coverage %60
Estimate: 2-3 hafta. Actual: 4 hafta (her zaman daha uzun sürüyor).
Migration phases
Phase 1: Warning mode
Xcode project settings: Swift Compiler > Upcoming Features > enable:
– BareSlashRegexLiterals
– ConciseMagicFile
– ImplicitOpenExistentials
– DisableOutwardActorInference
– StrictConcurrency ← kritik
Bu flag’le Swift 6 behavior warning olarak çıkıyor, build hâlâ geçiyor. Kod tabanındaki issue’ları görüyorsun.
Dentii’de ilk build: 1247 warning.
Phase 2: Fix in batches
1247 warning’i tek seferde fix edemiyorsun. Category’e göre grupla:
- Sendable conformance issues (~400)
- Main actor isolation (~300)
- Data race in closures (~200)
- Actor boundary crossing (~150)
- Other (~200)
Her kategori için ayrı commit ve PR. Bir kategoriyi tamamen fix et, merge et, sonraki geç.
Phase 3: Swift 6 mode enable
Son phase: Swift Language Mode = Swift 6. Warning’ler error’a dönüyor. Calışan build olmalı.
Eğer önceki phase’ler temiz ise bu straightforward. Değilse son 2-3 gün uğraş.
Sendable conformance
En yaygın issue. Bir type cross-actor gönderiliyorsa Sendable olmalı.
Problem:
class User {
var name: String
var email: String
}
// Warning: Non-sendable type cannot be sent across actors
Task {
let user = await fetchUser()
await mainActor.updateUI(user)
}Fix seçenekleri:
1. Struct’a çevir (if possible):
struct User: Sendable {
let name: String
let email: String
}2. Final class + Sendable:
final class User: Sendable {
let name: String // immutable
let email: String
}3. Actor olarak tanımla:
actor User {
var name: String
var email: String
}Hangi fix contextine göre:
– Data transfer object ise struct
– Reference semantics gerekliyse final class + immutable
– Mutating state varsa actor
MainActor isolation
UI code tek bir actor’da çalışmalı: MainActor. Swift 6 compiler bunu zorluyor.
Problem:
class ViewController: UIViewController {
var data: [Item] = []
func loadData() {
Task {
let items = try await api.fetchItems()
self.data = items // Warning: UI update off-main-thread
}
}
}Fix: MainActor annotation:
@MainActor
class ViewController: UIViewController {
var data: [Item] = []
func loadData() {
Task {
let items = try await api.fetchItems()
self.data = items // OK, class is MainActor-isolated
}
}
}UIKit ve SwiftUI view controllers/views MainActor olmalı. Swift 6 built-in olarak bunu varsayıyor ama legacy code’da eksik olabiliyor.
Data race in closures
En karmaşık category. Escaping closure’da mutable state capture.
Problem:
var counter = 0
DispatchQueue.global().async {
counter += 1 // Warning: data race
}Fix seçenekleri:
1. Actor’a taşı:
actor Counter {
var value = 0
func increment() { value += 1 }
}
let counter = Counter()
Task { await counter.increment() }2. @MainActor:
@MainActor var counter = 0
Task { @MainActor in
counter += 1
}3. Immutable + computed:
Yapılabiliyorsa state’i immutable yap, hesaplanan özellik olarak expose et.
Actor boundary crossing
Actor A’da olan bir method’un Actor B’ye parameter geçirmesi.
Problem:
actor DatabaseActor {
func save(_ user: User) async { /* ... */ }
}
actor NetworkActor {
func fetchUser() async -> User { /* ... */ }
}
// MainActor'dan çağrı
let user = await network.fetchUser()
await database.save(user) // User Sendable olmalıUser type Sendable değilse compiler uyarıyor. Fix: User’ı Sendable yap.
Third-party SDK issues
SDK’nın Swift 6 ready olmaması sık karşılaşılan issue.
Strategy:
1. Update to latest version. Çoğu popüler SDK (Firebase, Alamofire, RxSwift) artık ready.
2. -enable-experimental-feature NonSendableObjC flag. Objective-C bridge’de sendable check’i gevşetiyor.
3. Wrapper sınıf yaz. SDK API’ını kendi Sendable wrapper’ınla sar.
4. Bazen @preconcurrency import. Library’i legacy mode’da import.
Dentii’de eski bir analytics SDK sorun çıkardı. @preconcurrency import ile workaround, sonra SDK güncellenince kaldırdım.
Testing during migration
Migration boyunca test suite’i sürekli çalıştır. Regression erken yakala.
Concurrency-specific tests:
– Thread Sanitizer (TSan) Xcode’da açık
– Load tests (multiple concurrent operations)
– Race condition scenarios
Bu test’ler compile-time’da yakalanmayan runtime issue’ları yakalıyor.
Performance impact
Swift 6 concurrency olunca performance değişiyor mu?
Genelde hayır. Compile-time checks. Runtime’da extra overhead çok minimal.
Actor’lar message passing pattern. Function call’dan biraz daha yavaş ama 1ms altı fark.
Migration sonrası benchmark alın. Regression varsa specific actor’ları profile et.
Benefits after migration
Migration pain’i geçtikten sonra kazançlar:
- Data race imkansız. Compiler kanıtlıyor.
- Clearer code structure. Actor’lar state boundary’lerini net çiziyor.
- Better async code. Concurrency bugs azalıyor.
- Future-proof. Apple concurrency tooling’i Swift 6 üzerine kuracak.
Kısa vadeli pain, uzun vadeli kazanç.
Should I migrate now?
Swift 6 language mode şu an opt-in. Xcode 16’dan itibaren default olacak.
Migration önerisi:
– New projects: Swift 6 default, kod tabanı baştan Sendable-conscious.
– Mature projects (5+ years): phased migration, 2-3 release cycle.
– Small codebase (<20K LOC): aggressive migration, 1 hafta focused work.
– Large codebase (100K+ LOC): 1-2 aylık dedicated effort.
Hazır olmadıysan Xcode 16 default’unu kapat, erteleyerek devam. Ama 1-2 yıl içinde mandatory olacak.
Sonuç
Swift 6 concurrency migration inevitable. Apple net yönlendiriyor. Ne kadar gecikirsen o kadar pain bir noktada.
Phased approach: warning mode first, fix in batches, switch to Swift 6 mode last. 2-4 hafta for medium codebase.
Sendable, @MainActor, actors core concepts. Anlamadan migration başlatma. Kavramsal olgunlaşınca fix’ler mekanik hale geliyor.
End result: data-race-free, more maintainable code. Yatırımın karşılığı var.