• 16.08.2026 04:04:10
  • Admin Admin

SwiftUI uygulamalarında strict concurrency uyarılarını Actor ve Sendable ile kalıcı biçimde çözün. iOS MVVM mimarisi içinde izolasyon sınırlarını kurup Instruments ile task maliyetini ölçün.

SwiftUI'de Strict Concurrency: Actor, Sendable ve MVVM Geçişi

Xcode Eğitimi Perspektifiyle Strict Concurrency Denetimini Açmak

Bir ios eğitimi veya swift eğitimi içinde concurrency'yi yalnızca async/await sözdizimi olarak ele almak yetersizdir: asıl amaç, mutable verinin hangi executor üzerinde değiştiğini derleyiciye kanıtlamaktır. Xcode hedefinin Build Settings ekranında Strict Concurrency Checking değerini önce Complete yapın; CI'da aynı kontrolü görünür kılmak için derleme komutuna SWIFT_STRICT_CONCURRENCY=complete ekleyin. Uyarıları bir seferde susturmak yerine, her uyarıyı "bu değer hangi izolasyon alanına ait?" sorusuyla sınıflandırın: UI durumu MainActor, paylaşılan mutable cache actor, salt değer tipi DTO ise Sendable olur.

// CI veya yerel doğrulama: uygulama hedefini strict denetimle derle
xcodebuild   -scheme Catalog   -destination 'platform=iOS Simulator,name=iPhone 16'   SWIFT_STRICT_CONCURRENCY=complete   build

Migration sırasında en sık yapılan hata, tüm modülü veya her sınıfı @MainActor ile işaretlemektir. Bu yaklaşım derleyici uyarılarını azaltır ama JSON parse, görsel çözme ve disk erişimini main executor'a taşıyarak frame budget'ını tüketebilir. Önce Product > Analyze ile Sendable teşhislerini, sonra Scheme > Diagnostics altından Thread Sanitizer'ı açarak gerçek data race'leri ayrı ele alın. Thread Sanitizer'ın release performans ölçümü için değil, race bulmak için debug simülatör veya fiziksel cihaz koşusunda kullanılması gerekir; enstrümantasyonu yürütme maliyetini ciddi biçimde değiştirir.

iOS MVVM Mimarisi İçin Actor Sınırlarını Kurmak

iOS MVVM mimarisi içinde ViewModel'in ekran durumuna sahip olması onu doğal olarak @MainActor adayı yapar; ağ istemcisini de MainActor yapmak ise gereksiz seri kuyruklanma yaratır. Aşağıdaki düzenlemede UsersAPI kendi mutable olmayan URLSession erişimini actor izolasyonunda tutar, DTO değer tipi olduğu için actor sınırından güvenle geçer, ViewModel ise yalnızca yayınlanan UI durumunu main executor'da değiştirir.

import Foundation
import Combine

struct UserDTO: Decodable, Identifiable, Sendable {
    let id: Int
    let name: String
}

actor UsersAPI {
    func fetchUsers() async throws -> [UserDTO] {
        let url = URL(string: "https://api.example.com/users")!
        let (data, response) = try await URLSession.shared.data(from: url)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return try JSONDecoder().decode([UserDTO].self, from: data)
    }
}

@MainActor
final class UsersViewModel: ObservableObject {
    enum State: Sendable { case idle, loading, loaded([UserDTO]), failed }

    @Published private(set) var state: State = .idle
    private let api = UsersAPI()

    func load() {
        state = .loading
        Task {
            do {
                state = .loaded(try await api.fetchUsers())
            } catch is CancellationError {
                state = .idle
            } catch {
                state = .failed
            }
        }
    }
}

Bu ayrımın mekanizması önemlidir: actor'a yapılan her çağrı potansiyel bir suspension point'tir; çağrıdan sonra actor içindeki state'in başka bir task tarafından değişmiş olabileceğini varsaymalısınız. Örneğin cache actor'ında "önce oku, sonra ağdan getir, sonra yaz" akışında arada await varsa iki istek aynı anahtarı kaçırıp iki ağ çağrısı başlatabilir. Çözüm, actor içinde [Key: Task<Value, Error>] ile in-flight task coalescing yapmak veya bu işlemi tek, await'siz kritik geçişte kaydetmektir. Bu ayrıntı, aynı SwiftUI ekranının hızlıca iki kez açılmasıyla görülen yinelenen isteklerin tipik nedenidir.

SwiftUI Eğitimi İçin Task Yaşam Döngüsü ve İptal Kuralları

swiftui eğitimi örneklerinde sık görülen Task.detached, view yaşam döngüsünden kopuk olduğu için ekran kapanınca otomatik iptal edilmez ve actor bağlamını miras almaz. Ekrana bağlı yükleme için .task(id:) kullanın: kimlik değiştiğinde SwiftUI önceki task'i iptal eder, yeni task'i başlatır. ViewModel tarafında iptali yakalamak, gecikmeli bir sonucun artık görünmeyen ekrana state yazmasını engeller.

struct UserListScreen: View {
    @StateObject private var model = UsersViewModel()
    let organizationID: UUID

    var body: some View {
        List { /* state'i burada render edin */ }
            .task(id: organizationID) {
                model.load()
            }
    }
}

Buradaki edge case şudur: model.load() senkron bir metot olarak içerde yeni bir Task oluşturursa, dıştaki .task'in iptali iç task'e yapısal olarak aktarılmaz. İptal zincirini korumak için ViewModel API'sini func load() async yapıp doğrudan try await edin veya task handle'ını ViewModel'de saklayıp yeniden yüklemede açıkça loadTask?.cancel() çağırın. Bu, iphone uygulama geliştirme sürecinde arama alanına hızlı yazarken eski sorgu sonucunun yeni listeyi ezmesi sorununu deterministik olarak çözer.

Sendable Hatalarını @unchecked ile Gizlemeden Çözmek

swift kursu projelerinde legacy callback SDK'larını derleyici sessiz kalsın diye @unchecked Sendable ilan etmek, veri yarışını derleyicinin denetim alanından çıkarır. Bu öznitelik bir senkronizasyon mekanizması değildir; yalnızca "bu referansın tüm mutable durumunu ben koruyorum" iddiasıdır. Değişken cache veya sayaç için iddia etmek yerine actor kullanın; actor, erişimleri kendi seri executor'ında çalıştırarak read-modify-write işlemini atomik hale getirir.

actor TokenCache {
    private var token: String?

    func value() -> String? { token }
    func replace(with newValue: String?) { token = newValue }

    func invalidateIfEqual(to expected: String) {
        guard token == expected else { return }
        token = nil
    }
}

Bir diğer ince nokta closure capture'lardır. Bir callback'i @Sendable bekleyen API'ye verirken UIViewController, NSManagedObject veya mutable sınıf örneğini capture etmek izolasyon ihlalidir. Önce yalnızca gerekli immutable alanları çıkarın; örneğin let userID = user.id ile String yakalayın, UI güncellemesini ise await MainActor.run { ... } içinde yapın. Geçici olarak @preconcurrency import LegacySDK kullanılabilir, ancak bunu bir teknik borç kaydıyla sınırlayın: import uyarılarını bastırır, SDK'nın callback thread güvenliğini garanti etmez.

Instruments ile Actor Hop ve Task Maliyetini Ölçmek

Concurrency düzenlemesinin gecikmeye etkisini varsaymayın. Xcode Instruments'ta Swift Concurrency ve Time Profiler şablonlarıyla fiziksel cihaz üzerinde release yapıdan iki kayıt alın: önce mevcut akış, sonra actor sınırı düzenlenmiş akış. Her kayıtta aynı veri setiyle listeyi 10 kez açın; toplam task sayısını, executor hop'larını, en pahalı stack trace'i ve ilk görünür satıra kadar geçen süreyi karşılaştırın. Debug derlemesi, assertion ve daha düşük optimizasyon nedeniyle bu karşılaştırma için uygun baz çizgi değildir.

import os

private let feedLog = OSLog(subsystem: "com.example.catalog", category: "feed")

func loadFeed() async throws -> [UserDTO] {
    os_signpost(.begin, log: feedLog, name: "FeedLoad")
    defer { os_signpost(.end, log: feedLog, name: "FeedLoad") }
    return try await UsersAPI().fetchUsers()
}

Örneğin Instruments'ta bir satır görünümü başına ayrı Task açıldığını görürseniz, 200 satırlık listede yüzlerce kısa ömürlü task ve hop oluşur. Ağ sonucunu ViewModel'de tek seferde decode edip @Published koleksiyonu bir kez güncelleyin; satırların saf biçimlendirmesini ise senkron value-type fonksiyonunda yapın. Değişiklikten sonra aynı signpost aralığında task sayısı ve FeedLoad süresi düşmüyorsa optimizasyonu kabul etmeyin. Bu ölçüm disiplini, xcode eğitimi sırasında "actor ekledim, hızlandı" türü doğrulanamayan sonuçları engeller.

App Store Yayınlama Öncesi Concurrency Doğrulama Hattı

app store yayınlama öncesinde yalnızca UI testi yeşil olduğu için race olmadığını varsaymayın. CI hattında strict concurrency derlemesi, unit test ve Thread Sanitizer'lı ayrı bir test job'u çalıştırın. Sanitizer job'unu normal test süresinden bağımsız değerlendirin; instrumented runtime yavaşladığı için timeout değerlerini bu job'a özel tanımlayın.

xcodebuild test   -scheme Catalog   -destination 'platform=iOS Simulator,name=iPhone 16'   -enableThreadSanitizer YES   -enableCodeCoverage YES   SWIFT_STRICT_CONCURRENCY=complete

Test senaryolarına özellikle şu akışı ekleyin: istek başlat, ekranı kapat, aynı hesabı değiştir, uygulamayı yeniden foreground'a al. URLSession çoğu durumda iptali fırlatır; fakat disk cache'den veya legacy callback'ten gelen sonuç iptalden sonra da dönebilir. Bu nedenle state yazmadan hemen önce guard !Task.isCancelled else { return } kontrolü ve hesap kimliği eşleşmesi yapın. Bu kontrol, concurrency doğruluğunu ürün davranışına bağlar; yanlış kullanıcının verisinin kısa süreliğine görünmesini testte yakalamanızı sağlar.

Sık Sorulan Sorular

iOS kursu projelerinde Strict Concurrency Checking Complete ne zaman açılmalı?

Yeni modüllerde ilk günden Complete açın. Büyük mevcut kod tabanında hedefi modül modül taşıyın: önce DTO'ları Sendable yapın, sonra shared mutable servisleri actor'a alın, en son UI sahiplerini @MainActor işaretleyin. CI komutunda SWIFT_STRICT_CONCURRENCY=complete kullanarak yerel ayar farklarını önleyin.

Swift eğitimi sırasında @MainActor ve actor arasındaki pratik fark nedir?

@MainActor, UI state gibi main executor'a bağlı veriyi korur; actor ise kendi seri executor'ında paylaşılan mutable veriyi korur. Ağ istemcisini @MainActor yapmak parse ve cache güncellemelerini UI executor'ına bağlayabilir. ViewModel'i @MainActor, token cache'i actor yapmak daha dar ve ölçülebilir bir izolasyon sınırı verir.

SwiftUI eğitimi için .task mı Task.detached mı kullanılmalı?

View'ın yükleme işi için .task(id:) kullanın; SwiftUI görünüm kaybolduğunda veya id değiştiğinde task'i iptal eder. Task.detached actor bağlamını ve parent cancellation'ı miras almaz; yalnızca gerçekten bağımsız, Sendable girdili arka plan işi için seçin. Kararı Instruments Swift Concurrency kaydında task ömrü ve hop sayısıyla doğrulayın.

iOS MVVM mimarisi App Store yayınlama öncesi nasıl test edilmeli?

Release akışına xcodebuild test -enableThreadSanitizer YES ile ayrı bir race taraması ekleyin ve fiziksel cihazda Instruments Swift Concurrency kaydı alın. Testte istek sürerken logout, hesap değiştirme ve ekran kapatma senaryolarını çalıştırın; state güncellemesinden önce cancellation ile istek kimliği kontrolü yapın.

AI / LLM Discovery

Bu makale Opendart Akademi iOS / Swift eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt