Thread safety, multi-threaded programlamanın en zor problemidir. Data race'ler sinsi bug'lardır — bazen çalışır, bazen crash eder, genellikle debug'ı imkansızdır. Swift 5.5 ile gelen Actor modeli, bu sorunu dil seviyesinde çözer. Swift 6 ile birlikte ise data race safety varsayılan olarak zorunlu hale geliyor.
💡 Hızlı Not: Bu rehber WWDC21-24 Concurrency session'ları ve Swift Evolution proposal'larından derlendi. Swift 6 strict concurrency dahil.
İçindekiler
- Actor Model Nedir?
- Actor Tanımlama ve Kullanma
- Global Actors: @MainActor
- Sendable Protocol ve Data Race Safety
- Actor Isolation
- Structured Concurrency: Task Groups
- AsyncStream ve AsyncSequence
- Continuations: Callback'ten Async'e
- Actor Reentrancy
- Swift 6 Data Race Safety
Actor Model Nedir? {#actor-model}
Actor, mutable state'i koruyan ve concurrent erişimi serialize eden bir reference type'dır. Class'a benzer ama built-in thread safety ile gelir:
swift
1// ❌ Class - data race riski2class UnsafeCounter {3 var count = 04 func increment() { count += 1 } // Thread-unsafe!5}6 7// ✅ Actor - thread-safe garanti8actor SafeCounter {9 var count = 010 func increment() { count += 1 } // Actor-isolated11}12 13// Actor kullanımı - await gerekli14let counter = SafeCounter()15await counter.increment() // Async erişim16let value = await counter.countActor vs Class vs Struct
Özellik | Actor | Class | Struct |
|---|---|---|---|
Reference/Value | Reference | Reference | Value |
Thread safety | ✅ Built-in | ❌ Manual | ✅ (immutable) |
Mutable state | ✅ Güvenli | ⚠️ Data race | ✅ Copy-on-write |
Inheritance | ❌ Yok | ✅ Var | ❌ Yok |
async erişim | Gerekli | Gerekli değil | Gerekli değil |
Actor Tanımlama ve Kullanma {#actor-kullanim}
swift
1actor ImageCache {2 private var cache: [URL: UIImage] = [:]3 private let maxSize: Int4 5 init(maxSize: Int = 100) {6 self.maxSize = maxSize7 }8 9 func get(_ url: URL) -> UIImage? {10 cache[url]11 }12 13 func set(_ image: UIImage, for url: URL) {14 if cache.count >= maxSize {15 // LRU eviction16 cache.removeValue(forKey: cache.keys.first!)17 }18 cache[url] = image19 }20 21 // nonisolated - actor isolation'ı gerektirmeyen computed property22 nonisolated var description: String {23 "ImageCache(maxSize: \(maxSize))"24 }25}26 27// Kullanım28let cache = ImageCache()29if let cached = await cache.get(imageURL) {30 display(cached)31} else {32 let image = try await downloadImage(from: imageURL)33 await cache.set(image, for: imageURL)34 display(image)35}Global Actors: @MainActor {#global-actors}
@MainActor, UI güncellemelerinin main thread'de yapılmasını garanti eder:
swift
1// Tüm class main thread'de çalışır2@MainActor3class UserViewModel: ObservableObject {4 @Published var user: User?5 @Published var isLoading = false6 7 func loadUser() async {8 isLoading = true // Main thread ✅9 10 // Network call - otomatik background'a geçer11 let user = try? await fetchUser()12 13 self.user = user // Main thread ✅14 isLoading = false // Main thread ✅15 }16}17 18// Sadece tek fonksiyon19class DataProcessor {20 func processData() async -> [Result] {21 // Background thread'de ağır işlem22 let results = await heavyComputation()23 24 // UI güncelleme için MainActor'a geç25 await MainActor.run {26 updateUI(with: results)27 }28 29 return results30 }31}Sendable Protocol ve Data Race Safety {#sendable}
Sendable, bir değerin concurrent context'ler arasında güvenle paylaşılabileceğini belirtir:
swift
1// ✅ Otomatik Sendable - struct with Sendable properties2struct UserDTO: Sendable {3 let id: UUID4 let name: String5 let email: String6}7 8// ✅ Actor - otomatik Sendable9actor SessionManager: Sendable {10 var token: String?11}12 13// ❌ Class - Sendable değil (mutable reference type)14class UserManager {15 var users: [User] = [] // Data race riski!16}17 18// ✅ @Sendable closure19func performInBackground(_ work: @Sendable () async -> Void) {20 Task { await work() }21}22 23// ✅ @unchecked Sendable - manuel thread safety garanti ediyorsan24final class ThreadSafeArray<Element>: @unchecked Sendable {25 private var array: [Element] = []26 private let lock = NSLock()27 28 func append(_ element: Element) {29 lock.lock()30 defer { lock.unlock() }31 array.append(element)32 }33}Structured Concurrency: Task Groups {#structured-concurrency}
swift
1// Paralel API çağrıları2func loadDashboard() async throws -> Dashboard {3 async let profile = fetchProfile()4 async let orders = fetchOrders()5 async let recommendations = fetchRecommendations()6 7 return try await Dashboard(8 profile: profile,9 orders: orders,10 recommendations: recommendations11 )12}13 14// TaskGroup ile dinamik sayıda paralel iş15func downloadImages(urls: [URL]) async throws -> [UIImage] {16 try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in17 for (index, url) in urls.enumerated() {18 group.addTask {19 let (data, _) = try await URLSession.shared.data(from: url)20 return (index, UIImage(data: data)!)21 }22 }23 24 var images: [(Int, UIImage)] = []25 for try await result in group {26 images.append(result)27 }28 29 return images.sorted(by: { $0.0 < $1.0 }).map(\.1)30 }31}AsyncStream ve AsyncSequence {#async-stream}
swift
1// AsyncStream ile continuous data2func locationUpdates() -> AsyncStream<CLLocation> {3 AsyncStream { continuation in4 let manager = CLLocationManager()5 let delegate = LocationDelegate { location in6 continuation.yield(location)7 }8 manager.delegate = delegate9 manager.startUpdatingLocation()10 11 continuation.onTermination = { _ in12 manager.stopUpdatingLocation()13 }14 }15}16 17// Kullanım18for await location in locationUpdates() {19 updateMap(with: location)20}Continuations: Callback'ten Async'e {#continuations}
swift
1// Callback-based API'yi async'e çevir2func fetchImage(url: URL) async throws -> UIImage {3 try await withCheckedThrowingContinuation { continuation in4 URLSession.shared.dataTask(with: url) { data, _, error in5 if let error {6 continuation.resume(throwing: error)7 } else if let data, let image = UIImage(data: data) {8 continuation.resume(returning: image)9 } else {10 continuation.resume(throwing: ImageError.invalidData)11 }12 }.resume()13 }14}⚠️ Kritik: Continuation tam olarak bir kez resume edilmeli. Sıfır kez = memory leak, iki kez = crash!
Actor Reentrancy {#reentrancy}
Actor'lar reentrant'tır — bir await noktasında başka bir çağrı actor'a girebilir:
swift
1actor BankAccount {2 var balance: Double = 10003 4 func withdraw(_ amount: Double) async -> Bool {5 guard balance >= amount else { return false }6 // ⚠️ await sonrası balance değişmiş olabilir!7 balance -= amount8 return true9 }10}11// İki concurrent withdraw: ikisi de guard'ı geçebilir!12// Çözüm: await'ten sonra tekrar kontrol etSwift 6 Data Race Safety {#swift-6}
Swift 6'da -strict-concurrency varsayılan olarak aktif. Tüm data race'ler compile error:
swift
1// Swift 6'da bu compile etmez:2class Counter {3 var value = 0 // ❌ Sendable değil, concurrent erişim unsafe4}5 6// Çözümler:7actor Counter { var value = 0 } // ✅ Actor8struct Counter: Sendable { let value: Int } // ✅ Immutable structConcurrency Performans ve Debug İpuçları
Concurrency sorunlarını tespit etmek ve çözmek için kullanabileceğin araçlar ve teknikler:
swift
1// 1. Task priority kullanarak iş önceliği belirle2Task(priority: .userInitiated) {3 // Kullanıcı etkileşimi - yüksek öncelik4 await loadProfile()5}6 7Task(priority: .background) {8 // Arka plan işi - düşük öncelik9 await syncAnalytics()10}11 12// 2. Task cancellation'ı doğru kullan13class SearchViewModel: ObservableObject {14 private var searchTask: Task<Void, Never>?15 16 func search(query: String) {17 // Önceki aramayı iptal et18 searchTask?.cancel()19 searchTask = Task {20 try? await Task.sleep(for: .milliseconds(300))21 guard !Task.isCancelled else { return }22 let results = try? await api.search(query: query)23 guard !Task.isCancelled else { return }24 await MainActor.run { self.results = results ?? [] }25 }26 }27}28 29// 3. Custom global actor tanımla30@globalActor31actor DatabaseActor: GlobalActor {32 static let shared = DatabaseActor()33}34 35// Tüm database işlemleri bu actor'da çalışır36@DatabaseActor37class DatabaseService {38 func save(_ item: Item) async throws {39 // Bu fonksiyon DatabaseActor'da serialize edilir40 }41}Concurrency Debugging Araçları
Araç | Kullanım | Nasıl Aktifleştirilir |
|---|---|---|
**Thread Sanitizer (TSan)** | Data race tespiti | Scheme > Diagnostics > Thread Sanitizer |
**Instruments: Swift Concurrency** | Task yaşam döngüsü takibi | Instruments > Swift Concurrency template |
**SWIFT_DETERMINISTIC_HASHING** | Deterministik test sonuçları | Environment variable olarak ekle |
**Strict Concurrency Checking** | Compile-time data race uyarıları | Build Settings > Complete |
⚠️ Debug İpucu: Thread Sanitizer aktifken uygulamanın 2-10x yavaşladığını unutma. Sadece debug build'lerde kullan, release build'e asla ekleme.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [WWDC21: Protect mutable state with Swift actors](https://developer.apple.com/videos/play/wwdc2021/10133/) - [WWDC22: Eliminate data races using Swift Concurrency](https://developer.apple.com/videos/play/wwdc2022/110351/) - [SE-0306: Actors](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md) - [Swift 6 Migration Guide](https://www.swift.org/migration/documentation/migrationguide/)

