Swift Concurrency là gì? Hướng dẫn async/await và Actors cho iOS

Vì sao Swift concurrency là kỹ năng bắt buộc?

Swift concurrency là bộ tính năng giúp lập trình viên iOS viết mã bất đồng bộ an toàn và dễ đọc hơn. Từ Swift 5.5 trở đi, Apple giới thiệu async/await, actors và structured concurrency — thay thế hoàn toàn cách viết completion handler rối rắm trước đây.

Hiểu đúng Swift concurrency không chỉ giúp ứng dụng chạy mượt mà hơn mà còn tránh được hàng loạt lỗi race condition và data race khó debug. Bài viết này tổng hợp toàn bộ kiến thức nền tảng, kèm ví dụ cụ thể.

async/await — Trái tim của Swift concurrency

async/await cho phép viết code bất đồng bộ theo phong cách tuần tự, dễ đọc như code đồng bộ. Một hàm khai báo async có thể tạm dừng (suspend) và tiếp tục sau khi công việc nặng hoàn tất, giải phóng thread cho các tác vụ khác.

Cú pháp cơ bản

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

Điểm mấu chốt: từ khóa await đánh dấu nơi hàm có thể tạm dừng. Trong lúc chờ network, thread được trả về hệ thống thay vì bị block — đó là lý do UI không bị đơ.

Điều kiện sử dụng

  • Swift 5.5+ với Xcode 13+
  • iOS 15+ khi dùng trực tiếp; có thể back-deploy về iOS 13 từ Xcode 15.3
  • Hàm gọi hàm async bắt buộc phải nằm trong ngữ cảnh async

Structured concurrency — Task, TaskGroup và async let

Structured concurrency quản lý vòng đời của task theo cấu trúc phân cấp: task cha chờ toàn bộ task con hoàn tất, và hủy (cancel) tự động lan truyền xuống task con khi cần.

Task — Đơn vị công việc cơ bản

Task {
    let user = try await fetchUser(id: 42)
    print("User: \(user.name)")
}

async let — Chạy song song nhiều tác vụ độc lập

async let user = fetchUser(id: 42)
async let posts = fetchPosts(userId: 42)
let (userResult, postsResult) = try await (user, posts)

Hai network request chạy song song thay vì tuần tự — rút ngắn thời gian chờ gần một nửa.

TaskGroup — Nhóm động các tác vụ

let results = try await withThrowingTaskGroup(of: User.self) { group in
    for id in 1...10 {
        group.addTask { try await fetchUser(id: id) }
    }
    var users = [User]()
    for try await user in group {
        users.append(user)
    }
    return users
}

Actors — Bảo vệ dữ liệu dùng chung

Actor là kiểu dữ liệu mới bảo vệ trạng thái nội bộ khỏi data race. Mọi truy cập vào actor-isolated member đều đi qua hàng đợi tuần tự — hai task không bao giờ truy cập cùng lúc.

Ví dụ actor đếm truy cập

actor Counter {
    private var value = 0
    func increment() { value += 1 }
    func currentValue() -> Int { value }
}

Gọi từ bên ngoài actor phải dùng await:

let counter = Counter()
await counter.increment()
let current = await counter.currentValue()

@MainActor — Đưa mọi thứ về main thread

@MainActor
final class ViewModel: ObservableObject {
    @Published var items: [Item] = []
}

Mọi method của ViewModel tự động chạy trên main thread — không còn lo cảnh báo “UI updates must happen on main thread”.

Migration từ completion handler sang async/await

Cách viết cũ với completion handler:

func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error = error { completion(.failure(error)) }
        else { completion(.success(data ?? Data())) }
    }.resume()
}

Cách viết mới với async/await — gọn gàng và dễ kiểm soát lỗi hơn nhiều:

func fetchData() async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

Đối với API cũ không có phiên bản async, dùng withCheckedContinuation để bọc callback:

func legacyFetch() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        legacyFetch { result in
            continuation.resume(with: result)
        }
    }
}

Những lỗi thường gặp và cách tránh

  • Actor reentrancy: Actor tạm dừng giữa chừng khi gặp await — trạng thái có thể thay đổi trong lúc chờ. Kiểm tra lại điều kiện sau mỗi await
  • Block thread pool: Gọi code đồng bộ nặng (đọc file, tính toán) bên trong task async làm nghẽn cooperative thread pool. Chuyển sang Task.detached hoặc dùng dedicated executor
  • Bỏ quên cancellation: Kiểm tra Task.isCancelled trong vòng lặp dài, ném CancellationError khi cần
  • Sendable violation (Swift 6): Truyền dữ liệu không an toàn giữa các task bị compile error. Tuân thủ strict concurrency checking để bắt lỗi ngay lúc biên dịch
  • Gọi resume nhiều lần: withCheckedContinuation chỉ được resume đúng một lần — gọi hai lần là crash. Luôn kiểm tra mọi nhánh code

Ứng dụng thực tế trong app

  • Network layer: Kết hợp async let để fetch song song nhiều API, hiển thị UI ngay khi có dữ liệu
  • Database: SwiftData và Core Data hỗ trợ async context — thao tác database không còn block UI
  • Image loading: Tải và decode ảnh trong task riêng, cập nhật UI qua @MainActor
  • Streaming dữ liệu: Dùng AsyncStream cho location updates, notifications, dữ liệu realtime
  • WebSocket: AsyncSequence biến luồng message thành vòng lặp for-await dễ đọc

Yêu cầu phiên bản và lộ trình học

  • Swift 5.5 / Xcode 13: async/await, actors, structured concurrency, Task
  • Swift 5.7 / Xcode 14: Tinh chỉnh actor isolation, Sendable warnings
  • Swift 5.9 / Xcode 15: Back-deploy concurrency về iOS 13, macro hỗ trợ
  • Swift 6: Strict concurrency checking mặc định — data race bị chặn ngay lúc compile

Lộ trình học gợi ý: nắm async/await trước, sau đó đến Task và TaskGroup, rồi mới đụng actors. Cuối cùng là migration toàn bộ codebase lên Swift 6 strict concurrency.

Kết luận

Swift concurrency thay đổi cách lập trình viên iOS viết code bất đồng bộ theo hướng an toàn và dễ đọc hơn. Nắm vững async/await, structured concurrency và actors là yêu cầu cơ bản cho mọi dự án Swift hiện đại. Đặc biệt từ Swift 6, compiler sẽ ép buộc bạn viết concurrency-safe code — học sớm là lợi thế lớn.

Tham khảo thêm

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

WebAssembly là gì? Hướng dẫn toàn diện cho lập trình viên

WebAssembly là gì? Hướng dẫn toàn diện cho lập trình viên WebAssembly là định dạng mã nhị phân chạy gần tốc độ native trong trình duyệt, được W3C chuẩn hóa…

Xem thêm

Kotlin vs Swift: So sánh hai ngôn ngữ phát triển mobile

Kotlin và Swift: Hai ngôn ngữ phát triển mobile Kotlin và Swift là hai ngôn ngữ lập trình quan trọng nhất trong phát triển ứng dụng di động hiện nay….

Xem thêm

Message Queue là gì? So sánh RabbitMQ và Kafka cho người mới

Message Queue là hàng đợi tin nhắn, một hình thức giao tiếp bất đồng bộ giữa các dịch vụ trong kiến trúc microservices và serverless. Tin nhắn được lưu trên…

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