Ana Sayfa / Blog / Swift 6 concurrency migration: hazır mısın?

Swift 6 concurrency migration: hazır mısın?

Swift 6 strict concurrency checking default. 100K+ LOC codebase'i nasıl migrate ederim? Actors, Sendable, isolation anlamak.

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:

  1. Compile-time data race detection. İki thread aynı anda aynı data’ya erişiyor mu? Compiler uyarı veriyor.
  2. Sendable enforcement. Cross-actor geçen data’nın Sendable olması zorunlu. Thread-safe olduğunu garanti ediyor.
  3. Strict actor isolation. @MainActor, @globalActor annotation’ları zorunlu oldu.
  4. Complete concurrency checking. Swift 5.5’teki -strict-concurrency flag’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:

  1. Data race imkansız. Compiler kanıtlıyor.
  2. Clearer code structure. Actor’lar state boundary’lerini net çiziyor.
  3. Better async code. Concurrency bugs azalıyor.
  4. 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.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç