Swift 6 Async/Await & Structured Concurrency: Hướng Dẫn Từ Cơ Bản Đến Production

Swift 6 Async/Await & Structured Concurrency: Hướng Dẫn Từ Cơ Bản Đến Production-Ready

Swift 6 (ra mắt WWDC 2024) và Swift 6.2 (WWDC 2025) đưa Structured Concurrency trở thành default – async/await, Actors, Sendable, Task Groups, AsyncSequence trở thành bắt buộc cho code an toàn, không data race. Bài viết đi từ cơ bản đến pattern production, migration strategy, và best practices cho iOS/macOS developer.

Tại Sao Swift Concurrency Quan Trọng?

Trước Swift 5.5 (2021), async code dùng completion closures – dẫn đến “callback hell”, retain cycles, khó reasoning về execution order, data race tiềm ẩn. Swift Concurrency giải quyết:

  • Linear execution flow: Code async đọc như sync – dễ debug, dễ maintain
  • Compile-time data race safety: Swift 6 strict concurrency checking bắt lỗi tại compile time
  • Structured lifecycle: Task tự động cancel khi parent cancel – không leak resources
  • Actor isolation: Bảo vệ shared state không cần lock/manual sync

Async/Await Cơ Bản – Từ Closure Đến Structured

1. Async Method Definition

// Completion closure (legacy)
func fetchImages(completion: @escaping (Result) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error = error { completion(.failure(error)); return }
        let images = parse(data)
        completion(.success(images))
    }.resume()
}

// Async/await (modern) - Swift 5.5+
func fetchImages() async throws -> [UIImage] {
    let (data, _) = try await URLSession.shared.data(from: url)
    return parse(data)
}

Lợi ích: try-catch thay vì Result enum, linear flow, không retain cycle risk.

2. Await – Điểm Chờ (Suspension Point)

do {
    // 1. Gọi async method - execution suspend tại đây
    let images = try await fetchImages()
    // 2. Resume khi fetchImages hoàn tất (success hoặc throw)
    
    // 3. Gọi async method tiếp theo - tuần tự, dễ follow
    let resized = try await resizeImages(images)
    // 4. Resume
    
    print("Fetched (images.count) images, resized to (resized.count)")
} catch {
    // Bắt lỗi từ bất kỳ await nào trong do block
    print("Error: (error)")
}

Key insight: `await` đánh dấu suspension point – thread có thể làm việc khác trong lúc chờ. Không block thread như sync code.

So sánh execution flow: callback hell với nhiều cấp indentation vs async/await linear top-down
Execution order: Callback hell (unstructured) vs Async/Await (structured linear flow)

Structured Concurrency – Task Hierarchy & Cancellation

1. Structured vs Unstructured Tasks

// UNSTRUCTURED - Task detached, không tự cancel khi parent cancel
Task.detached {
    await longRunningOperation() // Có thể leak, outlive parent
}

// STRUCTURED - Child task, tự động cancel khi parent cancel
func parent() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask { await child1() } // Child task
        group.addTask { await child2() }
    } // Tự động wait all children, cancel all nếu parent cancel
}

2. Task Groups – Parallel Execution An Toàn


func fetchAllUserData(userIds: [String]) async throws -> [UserData] {
    try await withThrowingTaskGroup(of: UserData.self) { group in
        for id in userIds {
            group.addTask {
                try await fetchUser(id: id) // Mỗi task chạy parallel
            }
        }
        
        var results: [UserData] = []
        for try await userData in group {
            results.append(userData) // Collect results as they complete
        }
        return results
    }
}

Ưu điểm: Automatic cancellation propagation, exception handling tập trung, backpressure tự nhiên.

3. Async Let – Parallel Fire-and-Forget


// SEQUENTIAL - chậm (total = sum of each)
let user = try await fetchUser(id: "1")
let posts = try await fetchPosts(userId: "1")
let friends = try await fetchFriends(userId: "1")

// PARALLEL với async let - nhanh (total = max of each)
async let user = fetchUser(id: "1")
async let posts = fetchPosts(userId: "1")
async let friends = fetchFriends(userId: "1")

let (userResult, postsResult, friendsResult) = try await (user, posts, friends)

Xcode refactor options: Convert Function to Async, Add Async Alternative, Add Async Wrapper
Xcode refactor menu cho migration: Convert to Async, Add Async Alternative, Add Async Wrapper

Actors – Bảo Vệ Shared State Không Data Race

1. Actor Basics


// Actor = reference type với isolation built-in
actor ImageCache {
    private var cache: [URL: UIImage] = [:]
    private let maxSize = 100
    
    // Method mặc định isolated - chỉ 1 task truy cập tại 1 thời điểm
    func image(for url: URL) -> UIImage? {
        cache[url]
    }
    
    func setImage(_ image: UIImage, for url: URL) {
        if cache.count >= maxSize {
            cache.removeFirst() // Simple LRU
        }
        cache[url] = image
    }
    
    // Nonisolated - có thể gọi từ non-async context (read-only safe)
    nonisolated var count: Int { cache.count } // ⚠️ Chỉ dùng cho read-only computed property
}

// Usage - phải await vì actor-isolated
let cache = ImageCache()
let img = await cache.image(for: url)
await cache.setImage(newImage, for: url)

2. @MainActor – UI Updates An Toàn


// SwiftUI ViewModel - tất cả @Published updates trên MainActor
@MainActor
final class ContentViewModel: ObservableObject {
    @Published var images: [UIImage] = []
    @Published var isLoading = false
    
    func loadImages() {
        isLoading = true // MainActor - safe
        
        Task { // Implicit @MainActor inheritance
            do {
                let fetched = try await ImageService.shared.fetchAll()
                self.images = fetched // MainActor - safe UI update
            } catch {
                // handle error
            }
            self.isLoading = false
        }
    }
}

// Non-MainActor context gọi vào MainActor
func backgroundWork() {
    Task { @MainActor in // Explicit MainActor hop
        viewModel.loadImages()
    }
}

3. Global Actors – Custom Isolation Domain


// Custom global actor cho database operations
@globalActor
actor DatabaseActor {
    static let shared = DatabaseActor()
}

// Sử dụng
@DatabaseActor
func saveUser(_ user: User) async throws { ... }

@DatabaseActor
func fetchUser(id: String) async throws -> User { ... }

// Call site - tự động hop to DatabaseActor
func syncUser() async {
    try await saveUser(currentUser) // Runs on DatabaseActor
}

Sendable & Data Race Prevention – Swift 6 Strict Checking

1. Sendable Protocol

Type conform Sendable = an toàn để pass giữa concurrency domains (actors, tasks). Compiler kiểm tra:

  • Value types (struct, enum): Tự động Sendable nếu tất cả stored properties Sendable
  • Classes: Chỉ Sendable nếu final + immutable stored properties (let) + conform Sendable
  • Actors: Tự động Sendable (isolation đảm bảo safety)
  • Functions/@Sendable closures: Capture chỉ Sendable values

// ✅ Sendable - immutable value type
struct User: Sendable {
    let id: String
    let name: String
    let email: String
}

// ❌ KHÔNG Sendable - mutable class
class UserCache {
    var users: [String: User] = [:] // mutable stored property
}

// ✅ Sendable class - final + immutable
final class ImmutableConfig: Sendable {
    let apiEndpoint: URL
    let timeout: TimeInterval
    init(endpoint: URL, timeout: TimeInterval) {
        self.apiEndpoint = endpoint
        self.timeout = timeout
    }
}

// @Sendable closure - capture chỉ Sendable
func fetchData(completion: @Sendable (Result) -> Void) { ... }

2. @preconcurrency – Migration Gradual

Swift 6 strict checking có thể break existing code. Dùng @preconcurrency tạm thời:


// Import non-Sendable framework tạm thời
@preconcurrency import SomeLegacyFramework

// Hoặc attribute cho declaration
@preconcurrency
func legacyCallback(_ callback: @escaping (Result) -> Void) { ... }

AsyncSequence & AsyncStream – Streaming Data Async

1. AsyncSequence – Iterator Async


// Custom AsyncSequence cho pagination API
struct PaginatedUsers: AsyncSequence {
    typealias Element = User
    
    let apiClient: APIClient
    let initialRequest: UserListRequest
    
    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(client: apiClient, request: initialRequest)
    }
    
    struct AsyncIterator: AsyncIteratorProtocol {
        var client: APIClient
        var request: UserListRequest?
        
        mutating func next() async throws -> User? {
            guard let req = request else { return nil }
            let response = try await client.fetchUsers(req)
            request = response.nextPageRequest // nil nếu hết trang
            return response.users.first // yield từng user
        }
    }
}

// Usage - for await loop tự động handle pagination
for try await user in PaginatedUsers(apiClient: client, initialRequest: .first) {
    print(user.name) // Process each user as streamed
}

2. AsyncStream – Producer/Consumer Pattern


// Tạo stream từ callback-based API (NotificationCenter, delegate, etc.)
let notifications = AsyncStream { continuation in
    let observer = NotificationCenter.default.addObserver(
        forName: .dataDidChange, object: nil, queue: .main
    ) { notification in
        continuation.yield(notification)
    }
    
    continuation.onTermination = { @Sendable _ in
        NotificationCenter.default.removeObserver(observer)
    }
}

// Consumer
for await notification in notifications {
    await handleDataChange(notification)
}

Lỗi async call in function that does not support concurrency và giải pháp Task.init @MainActor
Lỗi thường gặp khi adopt async/await: ‘async’ call in function that does not support concurrency – giải pháp Task { @MainActor in … }

Migration Strategy: Từ Legacy Codebase Sang Swift 6

Bước 1: Enable Strict Concurrency Checking (Xcode 16+)

Build Settings → SWIFT_STRICT_CONCURRENCY = complete (hoặc SWIFT_UPCOMING_FEATURE_StrictConcurrency cho gradual). Compiler sẽ báo tất cả data race potential.

Bước 2: Fix Warnings – Priority Cao Đến Thấp

  1. Non-Sendable capture in @Sendable closure: Dùng @preconcurrency hoặc refactor capture
  2. Mutable state shared across actors: Bọc vào Actor hoặc dùng Mutex (Swift 6.2+)
  3. MainActor isolation violations: Thêm @MainActor hoặc Task { @MainActor in }
  4. Global mutable variables: Chuyển sang Actor hoặc @MainActor static property

Bước 3: Refactor Completion Callbacks → Async

Xcode 16 cung cấp 3 refactor options (Editor → Refactor):

  • Convert Function to Async: Thay thế hoàn toàn – dùng khi không cần backward compatibility
  • Add Async Alternative: Giữ legacy + thêm async version với @available(*, deprecated, renamed: "newAsync()") – khuyến nghị cho library/SDK
  • Add Async Wrapper: Async method mới gọi legacy completion-based qua withCheckedThrowingContinuation – ít code thay đổi nhất

Bước 4: Adopt Actors Cho Shared State

Identify mọi class có mutable state được access từ multiple concurrency domains → chuyển thành actor.

Bước 5: Test Với Thread Sanitizer (TSan)


# Scheme → Diagnostics → Thread Sanitizer
# Chạy unit tests + UI tests
# TSan detect data race at runtime (complement compile-time checking)

Best Practices Production-Ready

Pattern Khuyến Nghị Lý Do
Task creation Ưu tiên Task { } structured, hạn chế Task.detached Structured = automatic cancellation, leak prevention
Cancellation Check Task.isCancelled trong loop dài, respect CancellationError Tránh wasted work, resource leak
Timeout Dùng withTaskTimeout (Swift 6.2+) hoặc custom wrapper Tránh hang forever trên network call
Retry/Backoff Implement exponential backoff với Task.sleep (Swift 6.2: Task.sleep(for:)) Resilience cho network flaky
Testing Dùng await fulfillment(of: [expectation], timeout:) cho async test Deterministic async testing
Debugging Xcode 16: Debug navigator → Structured Concurrency view (task tree, suspension points) Visualize task hierarchy, cancellation flow

Common Pitfalls & Giải Pháp

1. “Async call in function that does not support concurrency”


// ❌ Sai - sync function gọi async
func viewDidLoad() {
    let data = try await fetchData() // Error!
}

// ✅ Đúng - wrap trong Task @MainActor
func viewDidLoad() {
    Task { @MainActor in
        do {
            let data = try await fetchData()
            updateUI(with: data)
        } catch { handleError(error) }
    }
}

2. Capture non-Sendable trong @Sendable closure


// ❌ Sai - capture self (non-Sendable class)
Task { @Sendable in
    self.updateUI() // self không Sendable
}

// ✅ Đúng - capture weak hoặc dùng Actor
Task { @MainActor [weak self] in
    self?.updateUI() // weak self Sendable (optional)
}

// Hoặc: actor-isolated method
actor UIUpdater {
    func update() { ... }
}
let updater = UIUpdater()
Task { await updater.update() } // Actor-isolated, safe

3. Forgetting to await – fire and forget bug


// ❌ Sai - không await, task chạy nền nhưng error bị swallow
func save() {
    Task { try await database.save(data) } // Error bị mất!
}

// ✅ Đúng - handle error hoặc return Task
func save() async throws {
    try await database.save(data)
}

// Hoặc explicit discard với error handling
func save() {
    Task {
        do { try await database.save(data) }
        catch { logger.error("Save failed: (error)") }
    }
}

Swift 6.2+ New Features (WWDC 2025)

  • Default Actor Isolation: Non-isolated async functions default chạy trên caller’s actor (giảm hop overhead)
  • @concurrent: Attribute cho phép function chạy concurrent an toàn (pure functions)
  • Mutex & Synchronization Framework: Mutex cho low-level sync (thay thế os_unfair_lock)
  • Task.sleep(for:) & Task.sleep(until:) - duration-based API thay vì nanoseconds
  • Testing improvements: await confirmation { }, await expect { } cho Swift Testing framework

Kết Luận

Swift 6 Structured Concurrency không chỉ syntax mới - là paradigm shift từ manual thread management sang compiler-guaranteed safety. Investment migration trả lời: data race = 0 tại compile time, cancellation tự động, code đọc hiểu như synchronous. Best approach: gradual adoption với @preconcurrency, Xcode refactor tools, TSan validation. Kết quả: codebase an toàn, maintainable, sẵn sàng cho multi-core Apple Silicon (M3/M4) tận dụng tối đa.

Nguồn: Async await in Swift explained - Antoine van der Lee | Migrating to Swift 6 - SwiftLee | Swift Concurrency Documentation | Swift 6.2 Concurrency Changes

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Ghostty: Terminal GPU-rendered thay thế iTerm2 và Alacritty

Ghostty: Terminal GPU-rendered thay thế iTerm2 và Alacritty Ghostty là terminal emulator mới phát hành phiên bản 1.0 vào cuối 2024, được viết bằng Zig và sử dụng GPU rendering…

Xem thêm

Feather Robotics: Nền tảng robot mô-đun giá 30k USD cho developer

Feather Robotics, khởi nghiệp humanoid do cựu kỹ sư Tesla và founder trước của 1X thành lập, đang định vị mình là “Android of robotics” — cung cấp nền tảng…

Xem thêm

Tauri 2.0: Ứng Dụng Desktop Rust Chạy Trên WebView Hệ Thống

Tauri 2.0: Ứng dụng Desktop Rust chạy trên WebView hệ thống Tauri 2.0 là bản phát hành ổn định tháng 10/2024 của framework ứng dụng desktop đa nền tảng mã…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất