Swift 6.2 Yaklaşılabilir Eşzamanlılık Rehberi: Bilmeniz Gereken Her Şey

Swift 6.2 ile gelen Yaklaşılabilir Eşzamanlılık özelliklerini keşfedin. Varsayılan MainActor izolasyonu, @concurrent, Observations API ve adım adım migrasyon rehberi ile Swift concurrency'yi kolayca öğrenin.

Giriş: Swift Eşzamanlılığında Yeni Bir Sayfa

Swift 6, async/await sözdizimi, aktör izolasyonu ve Sendable protokolü ile veri yarışlarını derleme zamanında yakalayan güçlü bir eşzamanlılık modeli getirdi. Gerçekten etkileyici bir sistemdi. Ama dürüst olmak gerekirse, birçoğumuz bu sistemin karmaşıklığıyla epey boğuştuk.

Swift 6.0 ve 6.1'de her sınıf ve fonksiyon için doğru aktör izolasyonunu elle belirtmek zorunda kalmak, hata mesajlarının anlaşılması güç olması ve beklenmedik thread geçişlerinin sebep olduğu performans sorunları... Bunlar geliştiricilerin sık karşılaştığı sıkıntılardı. Özellikle SwiftUI uygulamalarında neredeyse her view model'e @MainActor eklemek gerekiyordu — hem zahmetli hem de hata yapmaya açık bir süreçti.

İşte Swift 6.2, tam da bu noktada devreye giriyor.

"Yaklaşılabilir Eşzamanlılık" (Approachable Concurrency) adını verdikleri bu yenilik, Swift'in güçlü tip güvenliği garantilerinden taviz vermeden eşzamanlılığı çok daha sezgisel hale getiriyor. Artık Swift eşzamanlılığı sadece sistem programcılarının alanı değil; tüm iOS ve macOS geliştiricileri için erişilebilir bir teknoloji.

Önemli Not: Swift 6.2'deki Yaklaşılabilir Eşzamanlılık özellikleri, mevcut kodunuzu bozmadan çalışacak şekilde tasarlanmıştır. Yeni projeler otomatik olarak yararlanırken, mevcut projeler kademeli olarak geçiş yapabilir.

Varsayılan Aktör İzolasyonu: Ana Thread Öncelikli Yaklaşım

Swift 6.2'nin en dikkat çekici yeniliklerinden biri, SE-0466 önerisi ile gelen varsayılan aktör izolasyonu. Bu özellik doğrudan en yaygın kullanım senaryosuna odaklanıyor: kullanıcı arayüzü.

Sorun: Tekrar, Tekrar, Tekrar...

Swift 6.0 ve 6.1'de, SwiftUI view'ları ve UIKit view controller'ları ile çalışan her sınıfa @MainActor yazmak zorunluydu. Yeni başlayanlar için bu gerçekten kafa karıştırıcıydı:

// Swift 6.0/6.1'de zorunlu yaklaşım
@MainActor
class KullanıcıViewModel: ObservableObject {
    @Published var kullanıcılar: [Kullanıcı] = []
    @Published var yukleniyor = false

    func kullanıcılarıYukle() async {
        yukleniyor = true
        defer { yukleniyor = false }

        let veriServisi = VeriServisi()
        kullanıcılar = await veriServisi.kullanıcılarıGetir()
    }
}

@MainActor
class ProfilViewModel: ObservableObject {
    @Published var profil: Profil?
    @Published var hataMetni: String?

    func profilYukle(kullanıcıId: String) async {
        // Her ObservableObject sınıfı için @MainActor tekrarı
    }
}

Her yeni ViewModel oluşturduğunuzda @MainActor yazmayı unutmak çok kolaydı. Ve bu unutma, çalışma zamanında beklenmedik davranışlara ya da derleme hatalarına yol açıyordu.

Çözüm: Akıllı Varsayılanlar

Swift 6.2'de, Xcode 26 ile oluşturulan yeni projeler otomatik olarak "varsayılan MainActor izolasyonu" modunda başlıyor. Yani tüm sınıflar, struct'lar ve enum'lar varsayılan olarak MainActor üzerinde çalışıyor — tam da günlük uygulama geliştirmede ihtiyaç duyduğunuz gibi:

// Swift 6.2'de - @MainActor artık gerekli değil!
class KullanıcıViewModel: ObservableObject {
    @Published var kullanıcılar: [Kullanıcı] = []
    @Published var yukleniyor = false

    func kullanıcılarıYukle() async {
        yukleniyor = true
        defer { yukleniyor = false }

        // Otomatik olarak MainActor'da çalışır
        let veriServisi = VeriServisi()
        kullanıcılar = await veriServisi.kullanıcılarıGetir()
    }
}

// Başka bir ViewModel - yine @MainActor gereksiz
class ProfilViewModel: ObservableObject {
    @Published var profil: Profil?

    func profilYukle(kullanıcıId: String) async {
        // Varsayılan olarak ana thread'de
        profil = await ProfilServisi.shared.getir(id: kullanıcıId)
    }
}

Ne kadar temiz olduğunu görüyor musunuz?

Peki Nasıl Çalışıyor?

Derleyici, projenizin derleme ayarlarına göre bir "varsayılan izolasyon bağlamı" belirliyor. Xcode 26'da yeni bir proje oluşturduğunuzda bu bağlam otomatik olarak MainActor oluyor. Pratikte şu anlama geliyor:

  • Tüm global izole edilmemiş sınıflar, struct'lar ve enum'lar MainActor'da çalışıyor
  • ObservableObject, UIViewController, UIView gibi UI protokollerine uyan tipler için mükemmel uyum sağlanıyor
  • SwiftUI view'ları zaten MainActor'da çalıştığı için ekstra bir şey yapmanız gerekmiyor
  • Kodunuz daha temiz ve okuması çok daha kolay hale geliyor

Arka Plan İşlemleri İçin Opt-Out

Arka plan thread'lerinde çalışması gereken kodunuz varsa ne olacak? Swift 6.2'de bunun için açık mekanizmalar var:

// Aktör izolasyonundan tamamen çıkmak
nonisolated class ArkaplanServisi {
    func agirIslemYap() async -> Sonuc {
        // Esnek thread çalıştırma
        let veri = await hesaplamaYap()
        return veri
    }
}

// Veya actor kullanarak özel izolasyon
actor VeriOnbellegi {
    private var onbellek: [String: Data] = [:]

    func veriAl(anahtar: String) -> Data? {
        return onbellek[anahtar]
    }

    func veriKaydet(_ veri: Data, anahtar: String) {
        onbellek[anahtar] = veri
    }
}

"Varsayılan olarak güvenli, gerektiğinde esnek" — Swift eşzamanlılığının temel felsefesi tam da bu.

nonisolated(nonsending) ve Çağıran Bağlam Kalıtımı

Swift 6.2'nin en önemli tutarlılık iyileştirmelerinden biri SE-0461 ile gelen nonisolated fonksiyon davranış değişikliği. Açıkçası, bu değişiklik birçok geliştiricinin sık karşılaştığı (ve sinir olduğu) performans sorunlarını çözüyor.

Önceki Davranış ve Problemi

Swift 6.0 ve 6.1'de, nonisolated olarak işaretlenmiş asenkron fonksiyonlar her zaman global executor'da — yani arka plan thread'lerinde — çalışıyordu. Bu da bazen tamamen gereksiz thread geçişlerine yol açıyordu:

// Swift 6.0/6.1 davranışı
@MainActor
class HavaDurumuViewModel: ObservableObject {
    @Published var sicaklik: Double = 0.0

    func guncellemeyiBaslat() async {
        // MainActor'da başlıyoruz
        print("Thread 1: \(Thread.current)")

        let servis = HavaDurumuServisi()
        let veri = await servis.veriGetir() // ⚠️ Burada arka plana geçiş

        // Tekrar MainActor'a dönmek için otomatik geçiş
        print("Thread 2: \(Thread.current)")
        sicaklik = veri.sicaklik
    }
}

class HavaDurumuServisi {
    nonisolated func veriGetir() async -> HavaDurumuVerisi {
        // Swift 6.1'de: Global executor'da (arka plan)
        // Gereksiz thread geçişi!
        print("Servis Thread: \(Thread.current)")

        let url = URL(string: "https://api.hava.com/veri")!
        let (veri, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(HavaDurumuVerisi.self, from: veri)
    }
}

Görüyor musunuz sorunu? veriGetir() çağrıldığında tamamen gereksiz bir thread geçişi oluyor. ViewModel zaten MainActor'da ama nonisolated fonksiyon arka plana geçiyor, sonra sonuç MainActor'a geri dönüyor. Sık çağrılan fonksiyonlarda bu ciddi performans kaybına yol açıyordu.

Swift 6.2'deki Yeni Davranış

Swift 6.2'de nonisolated asenkron fonksiyonlar artık çağıran bağlamın aktör izolasyonunu miras alıyor. Daha verimli ve çok daha öngörülebilir:

// Swift 6.2 davranışı
@MainActor
class HavaDurumuViewModel: ObservableObject {
    @Published var sicaklik: Double = 0.0

    func guncellemeyiBaslat() async {
        print("Thread 1: \(Thread.current)") // main

        let servis = HavaDurumuServisi()
        let veri = await servis.veriGetir() // ✅ MainActor'da kalır!

        // Hala MainActor'da - gereksiz geçiş yok
        print("Thread 2: \(Thread.current)") // main
        sicaklik = veri.sicaklik
    }
}

class HavaDurumuServisi {
    nonisolated func veriGetir() async -> HavaDurumuVerisi {
        // Swift 6.2'de: Çağıranın bağlamında (MainActor)
        // Gereksiz geçiş yok!
        print("Servis Thread: \(Thread.current)") // main

        let url = URL(string: "https://api.hava.com/veri")!
        let (veri, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(HavaDurumuVerisi.self, from: veri)
    }
}

nonsending ile Kontrol Sizde

Bazı durumlarda fonksiyonunuzun hangi bağlamdan çağrıldığına bakılmaksızın belirli bir şekilde davranmasını isteyebilirsiniz. İşte o zaman nonisolated(nonsending) devreye giriyor:

class VeriYoneticisi {
    // Bu fonksiyon her zaman global executor'da çalışır
    nonisolated(nonsending) func topluIslemYap() async -> [Sonuc] {
        // Çağıran MainActor'da olsa bile arka planda çalışır
        // Çünkü bu ağır bir işlem ve ana thread'i bloklamamalı

        var sonuclar: [Sonuc] = []
        for i in 0..<10000 {
            let hesaplama = karmasikHesaplama(i)
            sonuclar.append(hesaplama)
        }
        return sonuclar
    }

    private func karmasikHesaplama(_ girdi: Int) -> Sonuc {
        return Sonuc(deger: girdi * girdi)
    }
}

Ne Zaman İşe Yarar?

Bu değişiklik özellikle şu senaryolarda fark yaratıyor:

  • Ağ çağrıları: URLSession zaten arka planda çalıştığı için sarmalayıcı fonksiyonun gereksiz geçiş yapmaması performansı ciddi artırıyor
  • Veri dönüşümleri: Basit JSON decode veya formatlama işlemleri çağıran bağlamda rahatlıkla kalabilir
  • Koordinasyon mantığı: Birden fazla servisi koordine eden fonksiyonlar gereksiz geçişlerden kurtulur
  • Önbellek kontrolleri: Hızlı cache aramaları çağıran thread'de kalır
class KullanıcıServisi {
    private var onbellek: [String: Kullanıcı] = [:]

    nonisolated func kullanıcıGetir(id: String) async -> Kullanıcı? {
        // Önce önbelleği kontrol et - çağıranın bağlamında
        if let onbelleklenmis = onbellek[id] {
            return onbelleklenmis // Hızlı dönüş, geçiş yok
        }

        // Önbellekte yoksa ağdan getir
        let url = URL(string: "https://api.example.com/kullanıcılar/\(id)")!
        let (veri, _) = try await URLSession.shared.data(from: url)
        let kullanıcı = try JSONDecoder().decode(Kullanıcı.self, from: veri)

        onbellek[id] = kullanıcı
        return kullanıcı
    }
}

@concurrent: Arka Planda Çalıştırmanın Açık Yolu

Varsayılan MainActor izolasyonu birçok durumda harika. Ama CPU yoğun işlemleri ana thread'de çalıştırmak? Bu kullanıcı deneyimini mahveder. İşte @concurrent niteliği tam da bunun için var.

@concurrent Nedir?

@concurrent, bir fonksiyonun her zaman arka plan thread'lerinde çalışacağını garanti eder. Çağıran bağlamdan tamamen bağımsızdır — fonksiyon MainActor'dan çağrılsa bile arka planda çalışır.

class ResimIslemcisi {
    // CPU yoğun işlem - her zaman arka planda çalışmalı
    @concurrent
    func resimiFiltreUygula(_ resim: UIImage, filtre: CIFilter) async -> UIImage? {
        guard let ciResim = CIImage(image: resim) else { return nil }
        filtre.setValue(ciResim, forKey: kCIInputImageKey)

        guard let cıktıResim = filtre.outputImage else { return nil }

        let context = CIContext(options: nil)
        guard let cgResim = context.createCGImage(cıktıResim, from: cıktıResim.extent) else {
            return nil
        }

        return UIImage(cgImage: cgResim)
    }

    @concurrent
    func topluResimIsle(_ resimler: [UIImage]) async -> [UIImage] {
        await withTaskGroup(of: UIImage?.self) { grup in
            for resim in resimler {
                grup.addTask {
                    let filtre = CIFilter.gaussianBlur()
                    filtre.radius = 10
                    return await self.resimiFiltreUygula(resim, filtre: filtre)
                }
            }

            var islenmisResimler: [UIImage] = []
            for await resim in grup {
                if let resim = resim {
                    islenmisResimler.append(resim)
                }
            }
            return islenmisResimler
        }
    }
}

Gerçek Dünya Örnekleri

Haydi, @concurrent kullanmanız gereken tipik senaryolara bakalım.

1. Büyük JSON İşlemleri

class VeriAyrıstırıcı {
    @concurrent
    func buyukJSONAyristir(_ veri: Data) async throws -> [Urun] {
        // Büyük JSON'ları decode etmek CPU yoğundur
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601

        let urunler = try decoder.decode([Urun].self, from: veri)

        return urunler.filter { $0.stokta }
                     .sorted { $0.fiyat < $1.fiyat }
    }
}

2. Veri Şifreleme

import CryptoKit

class GuvenlikYoneticisi {
    @concurrent
    func veriSifrele(_ veri: Data, anahtar: SymmetricKey) async throws -> Data {
        let sealedBox = try AES.GCM.seal(veri, using: anahtar)
        return sealedBox.combined ?? Data()
    }

    @concurrent
    func veriCoz(_ sifreliVeri: Data, anahtar: SymmetricKey) async throws -> Data {
        let sealedBox = try AES.GCM.SealedBox(combined: sifreliVeri)
        return try AES.GCM.open(sealedBox, using: anahtar)
    }
}

3. Dosya Sıkıştırma

import Compression

class DosyaYoneticisi {
    @concurrent
    func dosyaSikistir(_ girisURL: URL) async throws -> Data {
        let veri = try Data(contentsOf: girisURL)

        return try veri.withUnsafeBytes { tamponPointer in
            let kaynakTampon = tamponPointer.bindMemory(to: UInt8.self)
            let hedefBoyut = veri.count
            var hedefTampon = [UInt8](repeating: 0, count: hedefBoyut)

            let sikistirilmisBoyut = hedefTampon.withUnsafeMutableBytes { hedefPtr in
                let hedefPointer = hedefPtr.bindMemory(to: UInt8.self)
                return compression_encode_buffer(
                    hedefPointer.baseAddress!,
                    hedefBoyut,
                    kaynakTampon.baseAddress!,
                    veri.count,
                    nil,
                    COMPRESSION_LZMA
                )
            }

            guard sikistirilmisBoyut > 0 else {
                throw DosyaHatasi.sikistirmaBasarisiz
            }

            return Data(hedefTampon.prefix(sikistirilmisBoyut))
        }
    }
}

4. ML Çıkarımı

import CoreML

class MLYoneticisi {
    private let model: MLModel

    init(model: MLModel) {
        self.model = model
    }

    @concurrent
    func tahminYap(_ girisVerisi: MLFeatureProvider) async throws -> String {
        let cıktı = try model.prediction(from: girisVerisi)

        guard let sonuc = cıktı.featureValue(for: "sonuc")?.stringValue else {
            throw MLHatasi.gecersizCıktı
        }

        return sonuc
    }
}

Hangisini Ne Zaman Kullanmalı?

Bu üç özellik arasındaki farkları karıştırmak kolay, o yüzden kısa bir özet:

  • @concurrent: Her zaman arka planda çalışır. Çağıran bağlamdan bağımsız. Ağır CPU işleri için biçilmiş kaftan.
  • nonisolated(nonsending): Global executor'da çalışır ama çağıran aktörden bağımsız. Veri akışı kontrolü için ideal.
  • nonisolated (düz): Çağıran bağlamını miras alır. Hafif işlemler ve gereksiz geçişlerden kaçınmak için mükemmel.

Performans İpucu: Fonksiyonunuz 16ms'den uzun süren CPU işi yapıyorsa (yaklaşık 1 frame süresi), @concurrent kullanmayı düşünün. Uygulamanızın 60fps'de akıcı kalması buna bağlı olabilir.

İzole Edilmiş Uyumluluklar (Isolated Conformances)

SE-0470 ile gelen izole edilmiş uyumluluklar, protokol uyumluluklarının belirli bir aktöre kısıtlanmasını sağlıyor. Özellikle UI ile ilgili protokoller için tip güvenliğini ciddi artıran bir özellik.

Eskiden Nasıldı?

Swift 6.0'da bir protokole uyum sağladığınızda, bu uyumluluğun hangi aktör bağlamında çalışacağını belirlemek zordu:

// Swift 6.0/6.1'de belirsizlik
protocol VeriKaynagi {
    func veriGetir() async -> [Öge]
    func veriGuncelle(_ ögeler: [Öge]) async
}

class TabloViewModel: VeriKaynagi {
    var ögeler: [Öge] = []

    // ⚠️ Bu fonksiyonlar MainActor'da mı çalışıyor?
    func veriGetir() async -> [Öge] {
        return ögeler
    }

    func veriGuncelle(_ ögeler: [Öge]) async {
        self.ögeler = ögeler
        // UI güncellemesi yapılmalı - ama MainActor garantisi yok!
    }
}

Çözüm: @MainActor Protocol Conformance

Swift 6.2'de bir protokol uyumluluğunun tamamını belirli bir aktöre izole edebiliyorsunuz. Bu küçük sözdizimi değişikliği, büyük bir güvenlik kazancı sağlıyor:

protocol VeriKaynagi {
    func veriGetir() async -> [Öge]
    func veriGuncelle(_ ögeler: [Öge]) async
}

// İzole edilmiş uyumluluk - tüm fonksiyonlar MainActor'da
class TabloViewModel: @MainActor VeriKaynagi {
    var ögeler: [Öge] = []

    // ✅ Bu fonksiyonlar kesinlikle MainActor'da çalışır
    func veriGetir() async -> [Öge] {
        return ögeler
    }

    func veriGuncelle(_ ögeler: [Öge]) async {
        self.ögeler = ögeler
        // UI güncellemesi güvenli - MainActor garantisi var
        TabloGuncellemeleriYayinla()
    }
}

Farklı İzolasyonlarla Çoklu Uyumluluk

Bir tip aynı protokole farklı aktör izolasyonlarıyla birden fazla kez uyum sağlayabiliyor. Gelişmiş senaryolar için bu gerçekten güçlü bir araç:

protocol Gozlemlenebilir {
    func degisiklikBildir()
}

class VeriYoneticisi {
    private var veri: [String: Any] = [:]
}

// MainActor uyumluluğu - UI güncellemeleri için
extension VeriYoneticisi: @MainActor Gozlemlenebilir {
    func degisiklikBildir() {
        // MainActor'da - UI'ı güncelleyebilir
        NotificationCenter.default.post(
            name: .veriDegisti,
            object: nil
        )
    }
}

// Özel aktör uyumluluğu - arka plan işlemleri için
actor ArkaplanIslemci {
    let yonetici: VeriYoneticisi

    init(yonetici: VeriYoneticisi) {
        self.yonetici = yonetici
    }
}

extension VeriYoneticisi: Gozlemlenebilir {
    func degisiklikBildir() {
        print("Veri değişti")
    }
}

SwiftUI ile Entegrasyon

İzole edilmiş uyumluluklar SwiftUI protokolleri ile çalışırken gerçekten parlıyor:

protocol OzelGorunum {
    func gorselGuncelle(veri: GorselVeri)
    func boyutHesapla() -> CGSize
}

class GrafikKomponenti: @MainActor OzelGorunum {
    private var katman: CALayer = CALayer()

    func gorselGuncelle(veri: GorselVeri) {
        // ✅ MainActor garantisi - CALayer güvenli
        katman.contents = veri.resim.cgImage
        katman.frame = veri.cerceve
    }

    func boyutHesapla() -> CGSize {
        // ✅ MainActor'da - UIKit hesaplamaları güvenli
        return katman.preferredFrameSize()
    }
}

struct GrafikView: View {
    @StateObject private var komponent = GrafikKomponenti()

    var body: some View {
        Canvas { context, boyut in
            let gorselVeri = GorselVeri(
                resim: UIImage(systemName: "star.fill")!,
                cerceve: CGRect(origin: .zero, size: boyut)
            )
            komponent.gorselGuncelle(veri: gorselVeri)
        }
    }
}

Protokol Seviyesinde İzolasyon

Protokollerin kendileri de aktör izolasyonu gerektirebilir:

@MainActor
protocol UIGuncelleyici {
    func arayuzuGuncelle()
    func yeniDurumGoster(_ durum: Durum)
}

// Tüm uyumlar otomatik olarak MainActor'da
class EkranYoneticisi: UIGuncelleyici {
    func arayuzuGuncelle() {
        // MainActor garantili
    }

    func yeniDurumGoster(_ durum: Durum) {
        // MainActor garantili
    }
}

Mimari İpucu: UI ile doğrudan etkileşime giren protokoller için @MainActor izolasyonu kullanın. Veri işleme protokollerini ise izolasyonsuz bırakarak esneklik sağlayın.

Observations: Değişiklikleri İzlemenin Modern Yolu

Swift 6.2 ile birlikte @Observable makrosunda kullanılan yeni bir Observations tipi geliyor. Kısacası, reaktif programlama artık Swift'e doğal ve güvenli bir şekilde entegre.

Observations Nedir?

Observations, bir @Observable nesnesinin özelliklerindeki değişiklikleri asenkron akış olarak izlemenizi sağlayan bir AsyncSequence. Combine'daki Publisher kavramına benziyor ama async/await ile tam uyumlu. Ve inanın, kullanımı çok daha basit.

Temel Kullanım

import Observation

@Observable
class SayacModeli {
    var sayac: Int = 0
    var mesaj: String = "Başlangıç"

    func artir() {
        sayac += 1
        mesaj = "Sayaç: \(sayac)"
    }
}

func sayacIzle() async {
    let model = SayacModeli()

    // Sadece 'sayac' özelliğini izle
    let sayacGozlemleri = Observations { model.sayac }

    Task {
        for await deger in sayacGozlemleri {
            print("Yeni sayaç değeri: \(deger)")
        }
    }

    model.artir() // Çıktı: "Yeni sayaç değeri: 1"
    model.artir() // Çıktı: "Yeni sayaç değeri: 2"
    model.artir() // Çıktı: "Yeni sayaç değeri: 3"
}

Birden Fazla Özelliği Aynı Anda İzleme

Birden fazla özelliği tuple olarak izleyebilirsiniz:

@Observable
class KullanıcıDurumu {
    var kullanıcıAdi: String = ""
    var girisYapildi: Bool = false
    var profilResmi: URL?

    func girisYap(kullanıcı: String) {
        kullanıcıAdi = kullanıcı
        girisYapildi = true
    }
}

func durumIzle() async {
    let durum = KullanıcıDurumu()

    let durumGozlemleri = Observations {
        (durum.kullanıcıAdi, durum.girisYapildi)
    }

    Task {
        for await (ad, girisli) in durumGozlemleri {
            print("Kullanıcı: \(ad), Giriş: \(girisli)")
        }
    }

    durum.girisYap(kullanıcı: "[email protected]")
    // Çıktı: "Kullanıcı: [email protected], Giriş: true"
}

İşlemsel Güncellemeler

Observations'ın en güzel yanlarından biri işlemsel güncellemeleri desteklemesi. Birden fazla özelliği aynı anda değiştirdiğinizde sadece tek bir bildirim alıyorsunuz — gereksiz render'lardan kurtuluyorsunuz:

@Observable
class AlışverişSepeti {
    var urunler: [Urun] = []
    var toplamFiyat: Double = 0.0
    var indirimKodu: String?

    func urunEkle(_ urun: Urun) {
        withObservationTracking {
            urunler.append(urun)
            toplamFiyat += urun.fiyat
        }
        // Sadece bir bildirim gönderilir
    }

    func indirimUygula(_ kod: String, indirimOrani: Double) {
        withObservationTracking {
            indirimKodu = kod
            toplamFiyat *= (1.0 - indirimOrani)
        }
    }
}

func sepetIzle() async {
    let sepet = AlışverişSepeti()

    let gozlemler = Observations {
        (sepet.urunler.count, sepet.toplamFiyat)
    }

    Task {
        for await (urunSayisi, toplam) in gozlemler {
            print("\(urunSayisi) ürün, Toplam: \(toplam) TL")
        }
    }

    sepet.urunEkle(Urun(ad: "Laptop", fiyat: 25000))
    // Çıktı: "1 ürün, Toplam: 25000.0 TL"

    sepet.indirimUygula("YILBASI2024", indirimOrani: 0.15)
    // Çıktı: "1 ürün, Toplam: 21250.0 TL"
}

SwiftUI ile Entegrasyon

Observations, SwiftUI ile mükemmel uyum sağlıyor. Reaktif veri akışı için temiz ve anlaşılır bir yol sunuyor:

@Observable
class HavaDurumuModeli {
    var sehir: String = "İstanbul"
    var sicaklik: Double = 0.0
    var durum: String = "Güneşli"
    var sonGuncelleme: Date = Date()

    func guncelle() async {
        try? await Task.sleep(for: .seconds(1))
        sicaklik = Double.random(in: 15...30)
        durum = ["Güneşli", "Bulutlu", "Yağmurlu", "Karlı"].randomElement()!
        sonGuncelleme = Date()
    }
}

struct HavaDurumuView: View {
    @State private var model = HavaDurumuModeli()
    @State private var degisiklikSayisi = 0

    var body: some View {
        VStack(spacing: 20) {
            Text(model.sehir)
                .font(.largeTitle)

            Text("\(model.sicaklik, specifier: "%.1f")°C")
                .font(.system(size: 60))

            Text(model.durum)
                .font(.title2)

            Text("Değişiklik: \(degisiklikSayisi)")
                .foregroundStyle(.secondary)

            Button("Güncelle") {
                Task {
                    await model.guncelle()
                }
            }
        }
        .padding()
        .task {
            let gozlemler = Observations {
                (model.sicaklik, model.durum)
            }

            for await _ in gozlemler {
                degisiklikSayisi += 1
            }
        }
    }
}

Filtreleme ve Dönüştürme

AsyncSequence olduğu için standart async/await operatörleriyle gözlemleri filtreleyip dönüştürebilirsiniz:

@Observable
class SensorVerisi {
    var sicaklik: Double = 20.0
    var nem: Double = 50.0
}

func sensorIzle() async {
    let sensor = SensorVerisi()
    let gozlemler = Observations { sensor.sicaklik }

    // Sadece yüksek sıcaklıklarda uyar
    Task {
        for await sicaklik in gozlemler where sicaklik > 30.0 {
            print("⚠️ Yüksek sıcaklık: \(sicaklik)°C")
        }
    }

    // İlk 5 değişikliği izle
    Task {
        for await sicaklik in gozlemler.prefix(5) {
            print("Kayıt: \(sicaklik)°C")
        }
        print("İzleme tamamlandı")
    }

    sensor.sicaklik = 25.0 // Uyarı yok
    sensor.sicaklik = 32.0 // ⚠️ Uyarı!
    sensor.sicaklik = 35.0 // ⚠️ Uyarı!
}

Observations mı, Combine mı?

Combine'a aşinaysanız Observations'ın neden daha iyi olduğunu merak edebilirsiniz. Kısa cevap: daha az kod, daha fazla güvenlik.

  • Daha az kod: Publisher, Subscriber, Cancellable yapılarına gerek kalmıyor
  • Async/await entegrasyonu: Modern Swift eşzamanlılığı ile doğal çalışıyor
  • Tip güvenliği: Derleme zamanı garantisi, daha az çalışma zamanı hatası
  • Performans: Daha hafif ve verimli bir gözlemleme mekanizması
  • MainActor uyumu: SwiftUI ile sorunsuz entegrasyon

Migrasyon İpucu: Yeni projeler için kesinlikle Observations kullanın. Mevcut Combine kodunuzu ise kademeli olarak geçirmenizi öneririm — acele etmeye gerek yok.

Görev İsimlendirme ve Debug İyileştirmeleri

Eşzamanlı kodda hata ayıklamak bazen gerçekten sinir bozucu olabiliyor. Swift 6.2, bu konuda hayat kurtaran geliştirmeler getiriyor.

İsimlendirilmiş Görevler

Swift 6.2 öncesinde Task oluştururken ona isim veremiyordunuz. Xcode'un Debug Navigator'ında hepsi sadece "Task" olarak görünüyordu — hangisi hangisi, bilemiyordunuz:

// Swift 6.1 - isimsiz görevler
Task {
    let veri = await veriGetir()
    await islem(veri)
}

Task {
    await kullanıcıProfiliniGuncelle()
}

Task {
    await resimYukle()
}

// Xcode'da hepsi "Task" olarak görünür 😅

Swift 6.2 ile artık görevlerinize anlamlı isimler verebilirsiniz:

// Swift 6.2 - isimlendirilmiş görevler
Task(name: "Kullanıcı Verilerini Yükleme") {
    let veri = await veriGetir()
    await islem(veri)
}

Task(name: "Profil Güncelleme İşlemi") {
    await kullanıcıProfiliniGuncelle()
}

Task(name: "Profil Resmi Yükleme") {
    await resimYukle()
}

// Xcode'da her görev kendi ismiyle görünür

Karmaşık İş Akışlarında İsimlendirme

İsimlendirilmiş görevler, özellikle birden fazla paralel işlem çalıştırdığınız senaryolarda inanılmaz faydalı:

class UygulamaKoordinatoru {
    func uygulamayiBaslat() async {
        await withTaskGroup(of: Void.self) { grup in
            grup.addTask(name: "Kullanıcı Oturumu Kontrol") {
                await self.oturumKontrolEt()
            }

            grup.addTask(name: "Uzak Yapılandırma Yükleme") {
                await self.uzakYapılandırmaYukle()
            }

            grup.addTask(name: "Yerel Veritabanı Başlatma") {
                await self.veritabanınıBaslat()
            }

            grup.addTask(name: "Bildirim İzinleri Kontrol") {
                await self.bildirimIzinleriKontrol()
            }
        }
    }
}

Hiyerarşik Görev İsimleri

İç içe görev yapılarında isimlendirme, debug sürecini bambaşka bir seviyeye taşıyor:

func sepetıIsle() async {
    Task(name: "Alışveriş Sepeti İşleme") {
        Task(name: "Sepet - Ürün Doğrulama") {
            await urunleriDogrula()
        }

        Task(name: "Sepet - Fiyat Hesaplama") {
            await fiyatHesapla()
        }

        Task(name: "Sepet - Stok Kontrolü") {
            await stokKontrol()
        }

        // Xcode'da hiyerarşik görünüm:
        // Alışveriş Sepeti İşleme
        //   ├─ Sepet - Ürün Doğrulama
        //   ├─ Sepet - Fiyat Hesaplama
        //   └─ Sepet - Stok Kontrolü
    }
}

LLDB'de Gelişmiş Async Stepping

Bir de LLDB tarafında güzel haberler var. Swift 6.2'de asenkron kodda adım adım ilerleme çok daha güvenilir hale geldi. Artık await noktalarını geçerken bağlam kaybolmuyor:

func karmasikIsAkisi() async {
    print("1. Adım: Başlangıç")

    let kullanıcı = await kullanıcıGetir() // ✅ step-over güvenilir
    print("2. Adım: Kullanıcı alındı: \(kullanıcı.ad)")

    let siparisler = await siparisGetir(kullanıcı.id) // ✅ Bağlam korunuyor
    print("3. Adım: \(siparisler.count) sipariş bulundu")

    for siparis in siparisler {
        await siparisiIsle(siparis) // ✅ Döngü içinde de sorunsuz
        print("Sipariş işlendi: \(siparis.id)")
    }

    print("4. Adım: Tüm siparişler işlendi")
}

Önceki sürümlerde await noktasında step-over yaptığınızda debugger bazen kaybolabiliyor veya yanlış yere atlayabiliyordu. Swift 6.2'de bu sorunlar büyük ölçüde geride kaldı.

Debug Navigator'da Zengin Bağlam

Xcode'un Debug Navigator'ında artık görev bağlamı hakkında çok daha fazla bilgi var:

  • Görev adı: Sizin verdiğiniz okunabilir isim
  • Aktör izolasyonu: Görevin hangi aktörde çalıştığı
  • Öncelik: Görevin priority değeri
  • Üst görev: Parent task hiyerarşisi
  • Durum: Running, suspended veya completed
@MainActor
class VeriSenkronizasyonu {
    func senkronizeEt() async {
        Task(name: "Ana Senkronizasyon", priority: .high) {
            await withTaskGroup(of: Void.self) { grup in
                grup.addTask(priority: .medium, name: "Kullanıcı Verileri Sync") {
                    await self.kullanıcıVerileriSenkronize()
                }

                grup.addTask(priority: .medium, name: "Medya Dosyaları Sync") {
                    await self.medyaDosyalarıSenkronize()
                }
            }
        }
    }
}

Instruments ile Profiling

İsimlendirilmiş görevler Instruments'taki Swift Concurrency profiler'da da görünüyor. Performans sorunlarını teşhis etmek artık çok daha kolay:

func performansTestiYap() async {
    await withTaskGroup(of: Void.self) { grup in
        for i in 0..<100 {
            grup.addTask(name: "İşlem #\(i)") {
                await agirIslem()
            }
        }
        // Instruments'ta her şey görev adlarıyla etiketli!
    }
}

Debug İpucu: Karmaşık async operasyonlar için her zaman görev adları kullanın. Küçük bir çaba, hata ayıklama sürecinden saatler kazandırabilir.

Pratik Migrasyon Rehberi

Mevcut Swift 6.0 veya 6.1 projenizi Swift 6.2'ye taşımak düşündüğünüz kadar zor değil. Hatta şaşıracak kadar kolay diyebilirim. İşte adım adım nasıl yapacağınız.

Adım 1: Xcode ve Swift Sürümünü Güncelleyin

Öncelikle Xcode 26 veya daha yeni bir sürüme sahip olduğunuzdan emin olun. Ardından proje ayarlarında Swift Language Version'ı 6.2 olarak ayarlayın:

  • Xcode'da projenizi açın
  • Project Navigator'da projeyi seçin
  • Build Settings sekmesine gidin
  • Swift Language Version'ı "Swift 6.2" olarak ayarlayın

Adım 2: Varsayılan Aktör İzolasyonunu Etkinleştirin

Build Settings'e yeni bir bayrak ekleyin:

// Build Settings > Other Swift Flags'e ekleyin:
-enable-default-actor-isolation=main

Bu ayar tüm yeni kodunuzun varsayılan olarak MainActor izolasyonuna sahip olmasını sağlayacak.

Adım 3: Gereksiz @MainActor'ları Temizleyin

Artık birçok @MainActor anotasyonu gereksiz. Bunları güvenle kaldırabilirsiniz:

// ÖNCESİ - Swift 6.0/6.1
@MainActor
class AnaSayfaViewModel: ObservableObject {
    @Published var veriler: [Veri] = []
}

@MainActor
class ProfilViewModel: ObservableObject {
    @Published var profil: Profil?
}

// SONRASI - Swift 6.2
class AnaSayfaViewModel: ObservableObject {
    @Published var veriler: [Veri] = []
}

class ProfilViewModel: ObservableObject {
    @Published var profil: Profil?
}

Temiz, değil mi?

Adım 4: Arka Plan Servislerini İşaretleyin

MainActor'da çalışmaması gereken sınıfları nonisolated veya actor ile işaretleyin:

// ÖNCESİ
class VeriServisi {
    func agVeriGetir() async -> Data {
        // ...
    }
}

// SONRASI - Açıkça izolasyon dışına çıkarın
nonisolated class VeriServisi {
    func agVeriGetir() async -> Data {
        // Çağıran bağlamını miras alır
    }
}

// VEYA - Özel aktör izolasyonu
actor OnbellekServisi {
    private var onbellek: [String: Data] = [:]

    func veriAl(_ anahtar: String) -> Data? {
        return onbellek[anahtar]
    }
}

Adım 5: CPU Yoğun İşlemlere @concurrent Ekleyin

Ağır hesaplama yapan fonksiyonları belirleyip @concurrent ile işaretleyin:

// ÖNCESİ
class ResimIslemcisi {
    func filtreUygula(_ resim: UIImage) async -> UIImage? {
        // CPU yoğun işlem
    }
}

// SONRASI
class ResimIslemcisi {
    @concurrent
    func filtreUygula(_ resim: UIImage) async -> UIImage? {
        // Artık her zaman arka planda çalışır
        guard let ciResim = CIImage(image: resim) else { return nil }
        // ... filtre işlemleri
        return islenmisResim
    }
}

Adım 6: Combine'dan Observations'a Geçiş (İsteğe Bağlı)

Yeni yazdığınız kod için Observations'ı tercih edin. Ama mevcut Combine kodunu aceleye getirmeyin — kademeli geçiş yapın:

// ÖNCESİ - Combine
import Combine

class SayacViewModel: ObservableObject {
    @Published var sayac = 0
    private var cancellables = Set()

    init() {
        $sayac
            .sink { deger in
                print("Sayaç: \(deger)")
            }
            .store(in: &cancellables)
    }
}

// SONRASI - Observations
import Observation

@Observable
class SayacViewModel {
    var sayac = 0
}

func sayacIzle(viewModel: SayacViewModel) async {
    let gozlemler = Observations { viewModel.sayac }

    for await deger in gozlemler {
        print("Sayaç: \(deger)")
    }
}

Adım 7: Görev İsimlerini Ekleyin

Debug sürecinizi kolaylaştırmak için önemli Task'lara isim verin:

// ÖNCESİ
Task {
    await veriYukle()
}

// SONRASI
Task(name: "Başlangıç Verilerini Yükleme") {
    await veriYukle()
}

Adım 8: Test Edin

Migrasyon sonrası mutlaka tüm testlerinizi çalıştırın:

@MainActor
class ViewModelTests: XCTestCase {
    func testVeriYukleme() async throws {
        let viewModel = AnaSayfaViewModel()

        await viewModel.verileriYukle()

        XCTAssertFalse(viewModel.veriler.isEmpty)
        XCTAssertTrue(Thread.isMainThread)
    }
}

Sık Karşılaşılan Sorunlar ve Çözümleri

Sorun 1: Sendable Uyarıları

// Sorun
class VeriModeli { // ⚠️ Class 'VeriModeli' does not conform to Sendable
    var deger: String
}

// Çözüm 1: Struct kullanın (tercih edilen)
struct VeriModeli {
    var deger: String
}

// Çözüm 2: @unchecked Sendable (dikkatli kullanın)
class VeriModeli: @unchecked Sendable {
    let deger: String // immutable olmalı
}

Sorun 2: Actor İzolasyon İhlalleri

// Sorun
@MainActor
class ViewModel {
    var veri: String = ""
}

nonisolated func veriIsle(viewModel: ViewModel) {
    // ❌ Actor-isolated property 'veri' can not be referenced
    print(viewModel.veri)
}

// Çözüm
@MainActor
func veriIsle(viewModel: ViewModel) {
    // ✅ Sorun yok
    print(viewModel.veri)
}

Sorun 3: Closure İzolasyonu

// Sorun
@MainActor
class ViewModel {
    func islemBaslat() {
        Task {
            await arkaplanIslemi()
            // ❌ Burası MainActor'da olmayabilir
            self.sonucGoster()
        }
    }
}

// Çözüm
@MainActor
class ViewModel {
    func islemBaslat() {
        Task {
            let sonuc = await arkaplanIslemi()
            await MainActor.run {
                self.sonucGoster(sonuc)
            }
        }
    }
}

Migrasyon Kontrol Listesi

  • Xcode 26+ ve Swift 6.2'ye güncellendi
  • Varsayılan aktör izolasyonu etkinleştirildi
  • Gereksiz @MainActor anotasyonları temizlendi
  • Arka plan servisleri nonisolated veya actor olarak işaretlendi
  • CPU yoğun işlemler @concurrent ile işaretlendi
  • Önemli Task'lara isimler eklendi
  • Tüm testler başarıyla çalışıyor
  • Performans test edildi ve doğrulandı

Migrasyon Stratejisi: Projenizin tamamını bir anda değiştirmeye çalışmayın. Önce yeni kodunuzda Swift 6.2 özelliklerini kullanmaya başlayın, sonra kademeli olarak mevcut kodu güncelleyin. Bu yaklaşım hem daha güvenli hem daha az stresli.

Sonuç: Eşzamanlılık Artık Herkesin Erişiminde

Swift 6.2'nin Yaklaşılabilir Eşzamanlılık özellikleri, iOS ve macOS geliştirmede gerçekten önemli bir dönüm noktası. Apple, Swift 6'nın güvenlik garantilerinden taviz vermeden eşzamanlı programlamayı tüm geliştiriciler için erişilebilir hale getirmeyi başardı.

Neler Kazandık?

Varsayılan MainActor İzolasyonu sayesinde artık her ViewModel'e @MainActor yazmak zorunda değilsiniz. Derleyici en yaygın kullanım durumunu varsayılan davranış olarak benimsedi ve bu, özellikle yeni başlayanlar için öğrenme eğrisini ciddi şekilde düzleştirdi.

nonisolated(nonsending) ile gereksiz thread geçişleri tarih oldu. Fonksiyonlar çağıran bağlamda çalışarak daha verimli performans sunuyor.

@concurrent ile CPU yoğun işlemleri tek bir anotasyonla arka plana taşıyabiliyorsunuz. Resim işleme, şifreleme, ML çıkarımı — hepsi ana thread'i bloklamadan çalışıyor.

İzole Edilmiş Uyumluluklar, protokol temelli mimaride tip güvenliğini artırıyor. Derleme zamanında garanti — çalışma zamanı sürprizleri yok.

Observations API, Combine'ın karmaşıklığı olmadan reaktif programlamayı mümkün kılıyor. Daha az kod, daha fazla güvenlik.

Görev İsimlendirme ile karmaşık async akışlarda artık neyin ne yaptığını anlayabiliyorsunuz. Debug deneyimi tamamen değişti.

Geleceğe Bakış

Swift 6.2, eşzamanlılık evriminin son durağı değil. Gelecek sürümlerde daha akıllı derleyici çıkarımları, daha düşük actor geçiş maliyetleri, gelişmiş Xcode görselleştirme araçları ve server-side Swift için özel optimizasyonlar bekleyebiliriz.

Son Söz

Bence Swift 6.2, dilin en önemli felsefesini mükemmel yansıtıyor: güvenlik ve performanstan taviz vermeden kullanım kolaylığı. Artık eşzamanlı programlama her seviyedeki geliştirici için erişilebilir bir beceri.

Yeni başlayanlar karmaşık aktör izolasyonu kurallarıyla boğuşmadan güvenli kod yazabilecek. Deneyimli geliştiriciler daha az boilerplate ile daha okunabilir uygulamalar oluşturabilecek.

Swift 6.2'ye geçiş yapmak hem yeni hem mevcut projeler için mantıklı bir yatırım. Daha temiz kod, daha iyi performans ve gelişmiş debug deneyimi sizi bekliyor. Eğer henüz denemediyseniz, bugün yeni bir Swift 6.2 projesi oluşturun ve farkı kendi gözlerinizle görün.

Harekete Geçin: Bugün yeni bir Swift 6.2 projesi oluşturup bu özellikleri deneyin. Varsayılan MainActor izolasyonunun ne kadar rahatlık sağladığını, gereksiz @MainActor anotasyonları olmadan ne kadar temiz kod yazabildiğinizi göreceksiniz.