
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.detachedhoặc dùng dedicated executor - Bỏ quên cancellation: Kiểm tra
Task.isCancelledtrong vòng lặp dài, némCancellationErrorkhi 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:
withCheckedContinuationchỉ đượ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
AsyncStreamcho location updates, notifications, dữ liệu realtime - WebSocket:
AsyncSequencebiế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.
