Swift Structured Concurrency Mülakat Soruları: async/await, TaskGroup, Actors

Swift Structured Concurrency teknik mülakat soruları: async/await, TaskGroup, actors ve iOS 2026 için eşzamanlılık desenleri

async/await, TaskGroup ve actors ile Swift Structured Concurrency mülakat soruları

Swift 5.5 ile sunulan Structured Concurrency, iOS'taki asenkron programlamada devrim yarattı. İşe alım uzmanları artık teknik mülakatlarda async/await, TaskGroup ve actors hakimiyetini test ediyor. İşte mülakatlarda öne çıkmak için temel sorular ve beklenen cevaplar.

Mülakatlarda test edilen anahtar yetkinlikler

İşe alım uzmanları üç yetkinliği değerlendirir: temel kavramların anlaşılması (async/await, Task), eşzamanlılık desenlerinin hakimiyeti (TaskGroup, actor izolasyonu) ve yaygın hataları (data race, deadlock) teşhis etme yeteneği.

async/await ile DispatchQueue arasındaki fark nedir?

Beklenen cevap: async/await okunabilir sıralı kod ile yapılandırılmış eşzamanlılık sağlarken, DispatchQueue callback kullanır ve "callback hell" denilen duruma yol açabilir. Swift, async/await ile thread'leri otomatik olarak yönetir.

NetworkService.swiftswift
// Comparison: async/await vs DispatchQueue

// ❌ Old style: DispatchQueue with callbacks
func fetchUserOld(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
    DispatchQueue.global().async {
        // Simulated network call
        let result = self.performNetworkRequest(id: id)
        DispatchQueue.main.async {
            completion(result)
        }
    }
}

// ✅ New style: async/await more readable
func fetchUser(id: Int) async throws -> User {
    // Swift runtime handles threads automatically
    // No need to manually switch between queues
    return try await performNetworkRequest(id: id)
}

Anahtar noktalar: async/await callback piramitlerini ortadan kaldırır, thread hatalarını azaltır (DispatchQueue.main.async'e gerek yoktur) ve Swift runtime'ın mevcut CPU çekirdekleri arasında yürütmeyi optimize etmesine olanak tanır.

Performans avantajı

Swift runtime, aşırı thread oluşturmayı önleyen optimize bir thread havuzu kullanır. Her .async'in yeni bir thread oluşturabildiği DispatchQueue'nun aksine, async/await mevcut thread'leri akıllı şekilde yeniden kullanır.

Birden fazla asenkron işlem paralel olarak nasıl yönetilir?

Beklenen cevap: 2-3 basit görev için async let, sonuç toplama ile dinamik sayıda paralel görev için TaskGroup kullanın.

DataFetcher.swiftswift
// Parallel async operations strategies

struct DataFetcher {
    // Strategy 1: async let for fixed tasks (2-4 operations)
    func loadDashboard() async throws -> Dashboard {
        // Launch 3 requests in parallel
        async let user = fetchUser()
        async let posts = fetchPosts()
        async let notifications = fetchNotifications()

        // Wait for results (parallel, not sequential)
        let (userData, postsData, notificationsData) = try await (user, posts, notifications)

        return Dashboard(user: userData, posts: postsData, notifications: notificationsData)
    }

    // Strategy 2: TaskGroup for dynamic number of tasks
    func downloadImages(urls: [URL]) async throws -> [UIImage] {
        // TaskGroup allows managing N tasks with result collection
        try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
            // Launch one task per URL
            for (index, url) in urls.enumerated() {
                group.addTask {
                    let (data, _) = try await URLSession.shared.data(from: url)
                    guard let image = UIImage(data: data) else {
                        throw ImageError.invalidData
                    }
                    return (index, image) // Return index to preserve order
                }
            }

            // Collect results in order
            var images = [UIImage?](repeating: nil, count: urls.count)
            for try await (index, image) in group {
                images[index] = image
            }

            return images.compactMap { $0 }
        }
    }
}

Yaygın hata: await'i sıralı kullanmak, async let'in çağrıları paralelleştirmesi yerine geçer. let user = await fetchUser(); let posts = await fetchPosts() sıralı çalışır (yavaş), async let ise her ikisini eş zamanlı başlatır.

Actor nedir ve neden kullanılır?

Beklenen cevap: Actor, sıralı erişim garantisi vererek değişebilir durumunu data race'lerden koruyan bir tiptir. Eşzamanlı erişimi güvenceye almak için manuel kilitlerin (NSLock, DispatchQueue) yerini alır.

CacheManager.swiftswift
// Actor for thread-safe state management

// ❌ Classic class: data race risk
class UnsafeCache {
    private var cache: [String: Data] = [:] // Not thread-safe!

    func store(_ data: Data, for key: String) {
        cache[key] = data // ⚠️ Race condition with concurrent access
    }
}

// ✅ Actor: automatic protection against races
actor SafeCache {
    private var cache: [String: Data] = [:]

    // Sequential access guaranteed by actor isolation
    func store(_ data: Data, for key: String) {
        cache[key] = data // ✅ Thread-safe automatically
    }

    func retrieve(for key: String) -> Data? {
        return cache[key] // ✅ Protected read
    }

    // Internal synchronous method (no await needed)
    nonisolated func clearAll() async {
        // nonisolated allows calling from any context
        await self.clear()
    }

    private func clear() {
        cache.removeAll()
    }
}

// Usage: await required to access actor
let cache = SafeCache()
await cache.store(data, for: "user_123") // Await mandatory
let cachedData = await cache.retrieve(for: "user_123")

Anahtar noktalar: Actor, durumuna aynı anda yalnızca bir thread'in erişmesini garanti eder. Derleyici dış çağrılar için await kullanımını zorunlu kılar ve potansiyel askıya alma noktalarını açık hale getirir.

MainActor tuzağı

@MainActor, UI işlemleri için global bir actor'dur. Bir sınıfı @MainActor olarak işaretlemek, tüm metodlarını ana thread'de çalışmaya zorlar. Arayüzü dondurabilecek bloke edici çağrılara dikkat edin.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

TaskGroup'ta hatalar nasıl yönetilir?

Beklenen cevap: withThrowingTaskGroup karşılaşılan ilk hatayı yayar ve kalan görevleri otomatik olarak iptal eder. Tüm hataları toplamak için TaskGroup içinde Result kullanın.

BatchProcessor.swiftswift
// Error handling strategies in TaskGroup

struct BatchProcessor {
    // Strategy 1: First error propagation (fail-fast)
    func processItemsFastFail(items: [Item]) async throws -> [ProcessedItem] {
        try await withThrowingTaskGroup(of: ProcessedItem.self) { group in
            for item in items {
                group.addTask {
                    // If one task throws, group cancels others
                    try await self.process(item)
                }
            }

            // Collect results until first error
            var results: [ProcessedItem] = []
            for try await result in group {
                results.append(result)
            }
            return results
        }
        // ⚠️ If one task fails, others are cancelled
    }

    // Strategy 2: Collect all errors (resilience)
    func processItemsResilient(items: [Item]) async -> ([ProcessedItem], [Error]) {
        await withTaskGroup(of: Result<ProcessedItem, Error>.self) { group in
            for item in items {
                group.addTask {
                    // Wrap in Result to capture errors
                    do {
                        let result = try await self.process(item)
                        return .success(result)
                    } catch {
                        return .failure(error)
                    }
                }
            }

            // Separate successes/failures
            var successes: [ProcessedItem] = []
            var errors: [Error] = []

            for await result in group {
                switch result {
                case .success(let item):
                    successes.append(item)
                case .failure(let error):
                    errors.append(error)
                }
            }

            return (successes, errors)
        }
    }

    private func process(_ item: Item) async throws -> ProcessedItem {
        // Processing with possible error
        try await Task.sleep(nanoseconds: 100_000_000)
        return ProcessedItem(from: item)
    }
}

Anahtar nokta: withThrowingTaskGroup ilk hatada durur (atomik işlemler için yararlı), withTaskGroup + Result ise hatalara rağmen devam etmeyi sağlar (toplu işleme için yararlı).

Task, Task.detached ve async let arasındaki fark nedir?

Beklenen cevap: Task üst bağlamı miras alır (öncelik, actor izolasyonu), Task.detached mirassız bağımsız görev oluşturur, async let kapsam sonunda otomatik olarak beklenen alt görev oluşturur.

TaskLifecycle.swiftswift
// Understanding Task creation patterns

@MainActor
class ViewModel {
    var isLoading = false

    // Scenario 1: Task inherits context (@MainActor here)
    func loadDataWithTask() {
        Task {
            // ✅ Inherits @MainActor from parent
            // No need for await MainActor.run
            self.isLoading = true
            let data = try await fetchData()
            self.isLoading = false // ✅ Always on MainActor
        }
    }

    // Scenario 2: Task.detached creates independent task
    func loadDataDetached() {
        Task.detached {
            // ⚠️ Does NOT inherit @MainActor
            let data = try await self.fetchData()

            // ❌ Error: isLoading not directly accessible
            // await MainActor.run {
            //     self.isLoading = false
            // }
        }
    }

    // Scenario 3: async let creates structured child task
    func loadMultipleData() async throws {
        // async let tasks are bound to current scope
        async let users = fetchUsers()
        async let posts = fetchPosts()

        // ⚠️ If leaving function before await, compilation error
        let (usersData, postsData) = try await (users, posts)

        // async let tasks automatically cancelled
        // if exiting scope (e.g., throw before await)
    }

    private func fetchData() async throws -> Data {
        try await URLSession.shared.data(from: URL(string: "https://api.example.com")!).0
    }

    private func fetchUsers() async throws -> [User] { [] }
    private func fetchPosts() async throws -> [Post] { [] }
}

Kullanım senaryoları:

  • Task: Mevcut bağlama bağlı işlemler (örn. ViewModel'den UI güncellemesi)
  • Task.detached: Bağımsız arka plan görevleri (örn. log, analytics)
  • async let: Mevcut kapsamda sonuçları gereken paralel işlemler

Asenkron işlemde timeout nasıl uygulanır?

Beklenen cevap: withThrowingTaskGroup ile ana görev ve timeout görevi arasında bir yarışta Task.sleep kullanın veya withTimeout yardımcı fonksiyonu oluşturun.

AsyncTimeout.swiftswift
// Timeout implementation for async operations

enum TimeoutError: Error {
    case timedOut
}

// Generic utility to add timeout
func withTimeout<T>(
    seconds: TimeInterval,
    operation: @escaping () async throws -> T
) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        // Task 1: main operation
        group.addTask {
            try await operation()
        }

        // Task 2: timeout
        group.addTask {
            try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
            throw TimeoutError.timedOut
        }

        // First task to finish wins
        guard let result = try await group.next() else {
            throw TimeoutError.timedOut
        }

        // Cancel losing task (important for cleanup)
        group.cancelAll()

        return result
    }
}

// Usage example
struct NetworkService {
    func fetchUserWithTimeout(id: Int) async throws -> User {
        // 5-second timeout on network call
        try await withTimeout(seconds: 5) {
            try await self.fetchUser(id: id)
        }
    }

    private func fetchUser(id: Int) async throws -> User {
        let url = URL(string: "https://api.example.com/users/\(id)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(User.self, from: data)
    }
}

Modern alternatif: iOS 16'dan itibaren özellikle HTTP çağrıları için URLSessionConfiguration üzerinden yapılandırılmış timeoutInterval ile URLSession kullanın.

Açık iptal

group.cancelAll() kaynakları serbest bırakmak için kritiktir. Bu olmadan kaybeden görev arka planda doğal tamamlanmaya kadar devam eder ve CPU ile bellek israfına yol açar.

Birden fazla görev arasında değişebilir durum nasıl güvenli paylaşılır?

Beklenen cevap: Paylaşılan durum için actor veya görevler arası iletişim için AsyncStream üzerinden değer akışı kullanın.

SharedStateManager.swiftswift
// Safe state sharing between concurrent tasks

// Approach 1: Actor for shared state with sequential access
actor DownloadManager {
    private var activeDownloads: [String: Task<Data, Error>] = [:]
    private var cache: [String: Data] = [:]

    // Start download or return existing task
    func download(url: String) async throws -> Data {
        // Check cache first
        if let cachedData = cache[url] {
            return cachedData
        }

        // Check if download already in progress
        if let existingTask = activeDownloads[url] {
            return try await existingTask.value
        }

        // Create new download task
        let task = Task<Data, Error> {
            let data = try await self.performDownload(url: url)

            // Update cache (thread-safe via actor)
            await self.completeDownload(url: url, data: data)

            return data
        }

        activeDownloads[url] = task
        return try await task.value
    }

    private func performDownload(url: String) async throws -> Data {
        let urlObject = URL(string: url)!
        let (data, _) = try await URLSession.shared.data(from: urlObject)
        return data
    }

    private func completeDownload(url: String, data: Data) {
        cache[url] = data
        activeDownloads.removeValue(forKey: url)
    }
}

// Approach 2: AsyncStream for inter-task communication
struct EventStream {
    private let continuation: AsyncStream<Event>.Continuation
    let stream: AsyncStream<Event>

    init() {
        var continuation: AsyncStream<Event>.Continuation!
        stream = AsyncStream { cont in
            continuation = cont
        }
        self.continuation = continuation
    }

    func emit(_ event: Event) {
        continuation.yield(event)
    }

    func finish() {
        continuation.finish()
    }
}

// Example: shared progress monitoring
func processItemsWithProgress(items: [Item]) async {
    let eventStream = EventStream()

    // Task 1: Process items
    Task {
        for item in items {
            await processItem(item)
            eventStream.emit(.itemProcessed(item.id))
        }
        eventStream.finish()
    }

    // Task 2: Update UI with progress
    Task { @MainActor in
        for await event in eventStream.stream {
            switch event {
            case .itemProcessed(let id):
                print("Item \(id) processed")
            }
        }
    }
}

enum Event {
    case itemProcessed(String)
}

Mimari seçim: İş mantığı içeren merkezi durum için Actor, ayrıştırılmış bileşenler arasında olay tabanlı iletişim için AsyncStream.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Task iptali nedir ve nasıl yönetilir?

Beklenen cevap: Task iptali devam eden asenkron işlemleri iptal etmeyi sağlar. Görevlerin periyodik olarak Task.isCancelled'i kontrol etmesi veya hata fırlatan Task.checkCancellation()'i kullanması gerekir.

CancellableOperations.swiftswift
// Implementing proper task cancellation

struct ImageProcessor {
    // Cancellable processing with explicit checks
    func processImages(_ images: [UIImage]) async throws -> [ProcessedImage] {
        var results: [ProcessedImage] = []

        for (index, image) in images.enumerated() {
            // Check 1: Boolean check (continue or skip)
            if Task.isCancelled {
                print("Cancelled after \(index) images")
                break // Graceful stop
            }

            let processed = try await processImage(image)
            results.append(processed)

            // Check 2: Automatic throw if cancelled
            try Task.checkCancellation()
        }

        return results
    }

    private func processImage(_ image: UIImage) async throws -> ProcessedImage {
        // Simulate long processing
        for _ in 0..<10 {
            try await Task.sleep(nanoseconds: 100_000_000)

            // ✅ Check cancellation in long loops
            try Task.checkCancellation()
        }

        return ProcessedImage(from: image)
    }
}

// SwiftUI: Automatic cancellation when view disappears
struct ImageGalleryView: View {
    @State private var images: [ProcessedImage] = []

    var body: some View {
        ScrollView {
            // Display images
        }
        .task {
            // ✅ Task cancelled automatically when view disappears
            let processor = ImageProcessor()
            do {
                images = try await processor.processImages(sourceImages)
            } catch is CancellationError {
                print("Processing cancelled")
            }
        }
    }
}

// Manual cancellation of stored task
class DownloadViewModel {
    private var downloadTask: Task<Void, Never>?

    func startDownload() {
        downloadTask = Task {
            do {
                try await performLongDownload()
            } catch is CancellationError {
                print("Download cancelled by user")
            }
        }
    }

    func cancelDownload() {
        // Explicit cancellation of stored task
        downloadTask?.cancel()
        downloadTask = nil
    }

    private func performLongDownload() async throws {
        try Task.checkCancellation()
        // Download logic
    }
}

Anahtar noktalar:

  • Task.isCancelled: bloke etmeyen kontrol (bool döner)
  • Task.checkCancellation(): iptal edilmişse CancellationError fırlatır
  • SwiftUI değiştiricisi .task { }: view kaybolduğunda otomatik iptal
İşbirlikçi iptal

Swift işbirlikçi iptal modeli kullanır: görevler zorla sonlandırılmaz. Kod, iptale yanıt vermek için aktif olarak Task.isCancelled veya checkCancellation()'ı kontrol etmelidir. Bu kontroller olmadan görev süresiz devam eder.

Bir SwiftUI uygulamasında MainActor doğru nasıl kullanılır?

Beklenen cevap: Tüm UI durum güncellemelerinin ana thread'de gerçekleşmesini garanti etmek için ViewModel'leri @MainActor ile işaretleyin. Yalnızca belirli işlemler UI'a dokunuyorsa tek tek fonksiyonlarda @MainActor kullanın.

MainActorPatterns.swiftswift
// Proper MainActor usage in SwiftUI architecture

// Pattern 1: Entire ViewModel @MainActor
@MainActor
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var errorMessage: String?

    private let repository: UserRepository

    init(repository: UserRepository) {
        self.repository = repository
    }

    // ✅ All methods implicitly @MainActor
    func loadUser(id: Int) async {
        isLoading = true // No need for await or MainActor.run
        errorMessage = nil

        do {
            // Network call done on background thread by runtime
            user = try await repository.fetchUser(id: id)
        } catch {
            errorMessage = error.localizedDescription
        }

        isLoading = false // Always on MainActor
    }

    // Synchronous method also on MainActor
    func clearUser() {
        user = nil
        errorMessage = nil
    }
}

// Pattern 2: Selective methods with @MainActor
class DataSyncService {
    // ❌ Not @MainActor on class (no UI here)

    func syncData() async throws {
        // Background processing
        let data = try await fetchRemoteData()
        let processed = processData(data)

        // ✅ Switch to MainActor only for UI
        await updateUI(with: processed)
    }

    @MainActor
    private func updateUI(with data: ProcessedData) {
        // Update observable property
        NotificationCenter.default.post(
            name: .dataDidSync,
            object: data
        )
    }

    // Background work (not @MainActor)
    private func fetchRemoteData() async throws -> Data {
        // Network call
        Data()
    }

    private func processData(_ data: Data) -> ProcessedData {
        // CPU-intensive processing in background
        ProcessedData()
    }
}

// Pattern 3: Closure annotation
class ImageLoader {
    func loadImage(url: URL, completion: @MainActor @escaping (UIImage?) -> Void) async {
        let image = try? await downloadImage(from: url)

        // ✅ Completion guaranteed on MainActor
        await completion(image)
    }

    private func downloadImage(from url: URL) async throws -> UIImage {
        let (data, _) = try await URLSession.shared.data(from: url)
        return UIImage(data: data) ?? UIImage()
    }
}

Yaygın hata: Yalnızca belirli metodlar UI'a dokunurken tüm sınıfı @MainActor olarak işaretlemek. Bu, arka planda olması gereken ağır işlemler de dahil olmak üzere tüm kodu ana thread'e zorlar.

Sendable ile data race'ler nasıl yönetilir?

Beklenen cevap: Sendable protokolü, bir tipin görevler arasında data race riski olmadan paylaşılabileceğini garanti eder. Değer tipleri (struct, enum) otomatik olarak Sendable'dır, sınıflar değişmez veya korumalı özelliklerle final olmalıdır.

SendableCompliance.swiftswift
// Making types safe for concurrent access

// ✅ Struct: automatically Sendable (value type)
struct UserData: Sendable {
    let id: Int
    let name: String
    let email: String
}

// ✅ Enum: automatically Sendable
enum LoadingState: Sendable {
    case idle
    case loading
    case loaded(UserData)
    case failed(Error) // ⚠️ Error must also be Sendable
}

// ❌ Class with mutable state: not Sendable by default
class UnsafeCounter {
    var count = 0 // Mutable, unprotected

    func increment() {
        count += 1 // Data race possible
    }
}

// ✅ Immutable class: explicit Sendable
final class SafeConfig: @unchecked Sendable {
    let apiKey: String
    let timeout: TimeInterval

    init(apiKey: String, timeout: TimeInterval) {
        self.apiKey = apiKey
        self.timeout = timeout
    }
}

// ✅ Class with actor-protected state
actor SafeCounter: Sendable {
    private var count = 0 // Protected by actor isolation

    func increment() {
        count += 1 // Thread-safe automatically
    }

    func getValue() -> Int {
        return count
    }
}

// ✅ Class with manually protected state
final class ThreadSafeCache: @unchecked Sendable {
    private let lock = NSLock()
    private var storage: [String: Data] = [:]

    func store(_ data: Data, for key: String) {
        lock.lock()
        defer { lock.unlock() }
        storage[key] = data
    }

    func retrieve(for key: String) -> Data? {
        lock.lock()
        defer { lock.unlock() }
        return storage[key]
    }
}

// Usage: compiler checks Sendable
func processInBackground(data: UserData) { // ✅ UserData is Sendable
    Task.detached {
        // No warning: UserData is Sendable value type
        print("Processing user: \(data.name)")
    }
}

func processUnsafe(counter: UnsafeCounter) {
    Task.detached {
        // ⚠️ Warning: UnsafeCounter is not Sendable
        // counter.increment()
    }
}

Sendable kuralları:

  • Sendable özellikli Struct/Enum: otomatik olarak Sendable
  • Sınıflar: final + değişmez olmalı veya manuel koruma (lock, actor) ile @unchecked Sendable kullanmalı
  • Closure'lar: yalnızca Sendable tipleri yakaladığında otomatik olarak Sendable
@unchecked Sendable

@unchecked Sendable derleyici kontrollerini devre dışı bırakır. Yalnızca thread güvenliği manuel olarak garanti edildiğinde kullanın (lock, sıralı kuyruk). Data race'leri önlemek geliştiricinin sorumluluğundadır.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Sonuç

Swift Structured Concurrency hakimiyeti 2026 iOS mülakatları için vazgeçilmez hale geldi. İşe alım uzmanları üç seviyeyi test eder: kavramların anlaşılması (async/await vs callback), desenlerin hakimiyeti (TaskGroup, actor izolasyonu) ve hata ayıklama (iptal, Sendable).

Hazırlık kontrol listesi:

  • ✅ async/await vs DispatchQueue'yi somut bir örnekle açıklayın
  • ✅ Paralel işlemler için TaskGroup kullanımını gösterin
  • ✅ Değişebilir durumu korumak için thread-safe actor uygulayın
  • ✅ Eşzamanlı bağlamda hataları yönetin (Result, throwing)
  • ✅ Kullanım senaryolarıyla Task, Task.detached ve async let'i ayırın
  • ✅ Asenkron işlemde timeout uygulayın
  • ✅ SwiftUI mimarisinde MainActor'ı doğru kullanın
  • ✅ Sendable'ı anlayın ve data race'lerden kaçının

En iyi adaylar teori ve pratiği birleştirir: "neden"i (data race'lerden kaçınma, okunabilirliği iyileştirme) ve "nasıl"ı (hata yönetimli işlevsel kod) açıklarlar. Bu desenleri pekiştirmek için gerçek projelerde pratik yapın.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

#swift
#concurrency
#async-await
#actors
#interview

Paylaş

İlgili makaleler