Câu hỏi phỏng vấn Swift Structured Concurrency: async/await, TaskGroup, Actors

Các câu hỏi phỏng vấn kỹ thuật Swift Structured Concurrency: async/await, TaskGroup, actors và các mẫu concurrency cho iOS 2026

Câu hỏi phỏng vấn Swift Structured Concurrency với async/await, TaskGroup và actors

Structured Concurrency được giới thiệu trong Swift 5.5 đã cách mạng hóa lập trình bất đồng bộ trên iOS. Các nhà tuyển dụng hiện đánh giá khả năng làm chủ async/await, TaskGroup và actors trong các buổi phỏng vấn kỹ thuật. Dưới đây là những câu hỏi cốt yếu và câu trả lời được mong đợi để nổi bật trong các buổi phỏng vấn.

Năng lực then chốt được kiểm tra trong phỏng vấn

Nhà tuyển dụng đánh giá ba năng lực: hiểu biết các khái niệm cơ bản (async/await, Task), thành thạo các mẫu concurrency (TaskGroup, actor isolation) và khả năng chẩn đoán các lỗi phổ biến (data races, deadlocks).

Sự khác biệt giữa async/await và DispatchQueue là gì?

Câu trả lời mong đợi: async/await cung cấp structured concurrency với mã tuần tự dễ đọc, trong khi DispatchQueue sử dụng callback và có thể dẫn đến cái gọi là "callback hell". Swift tự động quản lý các thread với async/await.

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)
}

Điểm chính: async/await loại bỏ các kim tự tháp callback, giảm lỗi thread (không cần DispatchQueue.main.async) và cho phép runtime Swift tối ưu hóa việc thực thi trên các nhân CPU có sẵn.

Lợi thế hiệu năng

Runtime Swift sử dụng một thread pool tối ưu hóa, tránh tạo thread quá mức. Khác với DispatchQueue nơi mỗi .async có thể tạo một thread mới, async/await tái sử dụng các thread hiện có một cách thông minh.

Làm thế nào để quản lý nhiều thao tác bất đồng bộ song song?

Câu trả lời mong đợi: Sử dụng async let cho 2-3 task đơn giản, hoặc TaskGroup cho số lượng task song song động với việc thu thập kết quả.

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 }
        }
    }
}

Lỗi phổ biến: Sử dụng await tuần tự thay vì async let để song song hóa các lời gọi. let user = await fetchUser(); let posts = await fetchPosts() thực thi tuần tự (chậm), trong khi async let khởi chạy cả hai đồng thời.

Actor là gì và tại sao sử dụng nó?

Câu trả lời mong đợi: Actor là một kiểu bảo vệ trạng thái mutable của nó khỏi data races bằng cách đảm bảo truy cập tuần tự. Nó thay thế các lock thủ công (NSLock, DispatchQueue) để bảo vệ truy cập đồng thời.

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")

Điểm chính: Actor đảm bảo chỉ một thread truy cập trạng thái của nó tại một thời điểm. Compiler bắt buộc sử dụng await cho các lời gọi bên ngoài, làm cho các điểm tạm dừng tiềm năng trở nên rõ ràng.

Bẫy MainActor

@MainActor là một actor toàn cục cho các thao tác UI. Đánh dấu một class là @MainActor buộc tất cả các phương thức của nó thực thi trên main thread. Cẩn thận với các lời gọi blocking có thể đóng băng giao diện.

Sẵn sàng chinh phục phỏng vấn iOS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Làm thế nào để xử lý lỗi trong TaskGroup?

Câu trả lời mong đợi: withThrowingTaskGroup lan truyền lỗi đầu tiên gặp phải và tự động hủy các task còn lại. Để thu thập tất cả các lỗi, sử dụng Result trong TaskGroup.

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)
    }
}

Điểm chính: withThrowingTaskGroup dừng ở lỗi đầu tiên (hữu ích cho các thao tác nguyên tử), trong khi withTaskGroup + Result cho phép tiếp tục bất chấp các lỗi (hữu ích cho xử lý theo lô).

Sự khác biệt giữa Task, Task.detached và async let là gì?

Câu trả lời mong đợi: Task kế thừa ngữ cảnh cha (ưu tiên, actor isolation), Task.detached tạo task độc lập mà không kế thừa, và async let tạo child task tự động được chờ ở cuối phạm vi.

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] { [] }
}

Trường hợp sử dụng:

  • Task: Các thao tác gắn với ngữ cảnh hiện tại (ví dụ: cập nhật UI từ ViewModel)
  • Task.detached: Các task nền độc lập (ví dụ: log, analytics)
  • async let: Các thao tác song song với kết quả cần thiết trong phạm vi hiện tại

Làm thế nào để triển khai timeout cho một thao tác bất đồng bộ?

Câu trả lời mong đợi: Sử dụng Task.sleep trong một cuộc đua giữa task chính và task timeout với withThrowingTaskGroup, hoặc tạo một tiện ích withTimeout.

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)
    }
}

Giải pháp thay thế hiện đại: Từ iOS 16, sử dụng URLSession với timeoutInterval được cấu hình qua URLSessionConfiguration đặc biệt cho các lời gọi HTTP.

Hủy bỏ tường minh

group.cancelAll() rất quan trọng để giải phóng tài nguyên. Không có nó, task thua sẽ tiếp tục ở chế độ nền cho đến khi hoàn thành tự nhiên, lãng phí CPU và bộ nhớ.

Làm thế nào để chia sẻ trạng thái mutable một cách an toàn giữa nhiều task?

Câu trả lời mong đợi: Sử dụng actor cho trạng thái chia sẻ, hoặc AsyncStream để giao tiếp giữa các task qua một luồng giá trị.

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)
}

Lựa chọn kiến trúc: Actor cho trạng thái tập trung với logic nghiệp vụ, AsyncStream cho giao tiếp dựa trên sự kiện giữa các thành phần được tách rời.

Sẵn sàng chinh phục phỏng vấn iOS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Hủy bỏ Task là gì và làm thế nào để xử lý nó?

Câu trả lời mong đợi: Hủy bỏ Task cho phép hủy các thao tác bất đồng bộ đang chạy. Các task phải định kỳ kiểm tra Task.isCancelled hoặc sử dụng Task.checkCancellation() ném ra một lỗi.

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
    }
}

Điểm chính:

  • Task.isCancelled: kiểm tra không chặn (trả về bool)
  • Task.checkCancellation(): ném CancellationError nếu đã hủy
  • Modifier SwiftUI .task { }: tự động hủy khi view biến mất
Hủy bỏ hợp tác

Swift sử dụng mô hình hủy bỏ hợp tác: các task không bị buộc dừng. Mã phải chủ động kiểm tra Task.isCancelled hoặc checkCancellation() để phản ứng với việc hủy bỏ. Không có những kiểm tra này, task tiếp tục vô thời hạn.

Làm thế nào để sử dụng MainActor một cách chính xác trong ứng dụng SwiftUI?

Câu trả lời mong đợi: Chú thích các ViewModel với @MainActor để đảm bảo tất cả các cập nhật trạng thái UI xảy ra trên main thread. Sử dụng @MainActor trên các hàm riêng lẻ nếu chỉ một số thao tác chạm vào UI.

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()
    }
}

Lỗi phổ biến: Đánh dấu toàn bộ class là @MainActor khi chỉ một số phương thức chạm vào UI. Điều này buộc tất cả mã chạy trên main thread, bao gồm các thao tác nặng nên ở background.

Làm thế nào để xử lý data race với Sendable?

Câu trả lời mong đợi: Giao thức Sendable đảm bảo rằng một kiểu có thể được chia sẻ giữa các task mà không có nguy cơ data race. Các kiểu giá trị (struct, enum) tự động là Sendable, các class phải là final với các thuộc tính không thay đổi hoặc được bảo vệ.

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()
    }
}

Quy tắc Sendable:

  • Struct/Enum với thuộc tính Sendable: tự động Sendable
  • Class: phải final + không thay đổi, hoặc sử dụng @unchecked Sendable với bảo vệ thủ công (lock, actor)
  • Closure: tự động Sendable nếu chỉ bắt giữ các kiểu Sendable
@unchecked Sendable

@unchecked Sendable vô hiệu hóa các kiểm tra của compiler. Chỉ sử dụng nếu thread-safety được đảm bảo thủ công (lock, hàng đợi tuần tự). Trách nhiệm của developer là tránh data race.

Sẵn sàng chinh phục phỏng vấn iOS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Kết luận

Làm chủ Swift Structured Concurrency đã trở nên thiết yếu cho các buổi phỏng vấn iOS vào năm 2026. Các nhà tuyển dụng đánh giá ba cấp độ: hiểu biết các khái niệm (async/await vs callback), thành thạo các mẫu (TaskGroup, actor isolation) và debug (hủy bỏ, Sendable).

Danh sách kiểm tra chuẩn bị:

  • ✅ Giải thích async/await vs DispatchQueue với ví dụ cụ thể
  • ✅ Trình bày việc sử dụng TaskGroup cho các thao tác song song
  • ✅ Triển khai actor thread-safe để bảo vệ trạng thái mutable
  • ✅ Xử lý lỗi trong ngữ cảnh đồng thời (Result, throwing)
  • ✅ Phân biệt Task, Task.detached và async let với các trường hợp sử dụng
  • ✅ Triển khai timeout cho thao tác bất đồng bộ
  • ✅ Sử dụng MainActor đúng cách trong kiến trúc SwiftUI
  • ✅ Hiểu Sendable và tránh data race

Những ứng viên hàng đầu kết hợp lý thuyết và thực hành: giải thích "tại sao" (tránh data race, cải thiện khả năng đọc) và "làm thế nào" (mã chức năng với xử lý lỗi). Thực hành trên các dự án thực tế để củng cố các mẫu này.

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thẻ

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

Chia sẻ

Bài viết liên quan