Swift 6 Structured Concurrency: Viết code iOS an toàn, không data race

Swift 6: Thay đổi lớn nhất là concurrency

Swift 6, phát hành năm 2024, mang theo strict concurrency checking — compiler bắt buộc kiểm tra data race trước khi compile thành công. Đây là bước tiến lớn nhất từ Swift 5.x, giúp phát hiện race condition sớm thay vì debug thủ công trên thiết bị thật.

Trước Swift 6, Sendable chỉ là warning. Swift 6 biến nó thành error: nếu type không tuân thủ concurrency rules, code sẽ không compile. Thay đổi này ảnh hưởng toàn bộ codebase — đặc biệt là các app dùng async/await, Task, actor.

Xcode showing Swift 6 concurrency compiler error

Data race là gì?

Data race xảy ra khi 2 thread cùng truy cập cùng memory, ít nhất 1 write, không có synchronization — compiler không thể biết thứ tự thực thi nào đúng.

Ví dụ kinh điển:

var balance = 0

Task {
    for _ in 0..<1000 { balance += 1 }
}

Task {
    for _ in 0..<1000 { balance -= 1 }
}

// balance có thể là -989, 7, 1023 — không predict được

Trên Swift 5, code này compile thành công, chạy lung tung, rất khó debug. Trên Swift 6, compiler báo lỗi ngay: Capture of non-sendable type 'Int' in a @Sendable closure.

Swift 5 vs Swift 6 concurrency comparison diagram

Swift 6 concurrency rules

3 rule cốt lõi compiler kiểm tra:

  1. Sendable conformance: Type có thể truyền qua isolation boundary (task, actor, main actor) phải tuân thủ Sendable protocol — value semantics, internal state immutable hoặc protected.
  2. Sendable closures: Closure passed vào Task, TaskGroup, withTaskGroup phải có @Sendable — capture only Sendable values, no mutable reference.
  3. Isolation domains: Mỗi actor (bao gồm MainActor) là isolation domain riêng. Không được truy cập state của actor khác từ bên ngoài mà không có await.

Sendable types: value vs reference

Value types (struct, enum) tự động Sendable nếu tất cả properties cũng Sendable:

struct User: Sendable {    // OK
    let id: Int
    let name: String
}

Reference types (class) cần đánh dấu final + properties immutable để auto-conformance:

final class Config: Sendable {    // OK
    let apiKey: String
    let timeout: TimeInterval
}

Nếu class có mutable state, dùng actor thay vì class:

actor BankAccount {
    private var balance: Int = 0    // mutable, nhưng protected bởi actor isolation

    func deposit(_ amount: Int) { balance += amount }
    func withdraw(_ amount: Int) -> Bool { ... }
}

Compiler tự động đảm bảo mọi gọi đến BankAccount phải qua await — không ai đọc/ghi balance đồng thời từ bên ngoài.

Global actor: MainActor thay vì DispatchQueue.main

Swift 6 khuyến khích dùng MainActor thay vì DispatchQueue.main.async:

// Swift 5 cũ
DispatchQueue.main.async {
    self.label.text = "Updated"
}

// Swift 6
@MainActor
class ProfileViewController: UIViewController {
    func updateUI() {
        label.text = "Updated"    // tự động chạy trên main thread
    }
}

@MainActor đảm bảo toàn bộ type chạy trên main thread — không cần gọi DispatchQueue.main thủ công. Swift compiler tự động inject hop sang main thread khi cần.

Nếu chỉ cần 1 function chạy main, dùng @MainActor annotation riêng:

@MainActor
func updateLabel() {
    label.text = "Done"
}

TaskGroup: parallel execution an toàn

async letTaskGroup cho phép parallel task — Swift 6 đảm bảo mỗi task có riêng data, không share mutable state:

func fetchAll() async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        group.addTask { try await fetchUser(id: 1) }
        group.addTask { try await fetchUser(id: 2) }
        group.addTask { try await fetchUser(id: 3) }

        var results: [User] = []
        for try await user in group {
            results.append(user)    // thread-safe, group quản lý synchronization
        }
        return results
    }
}

Swift 6 còn giới thiệu sending parameter — value được “chuyển ownership” sang task, không còn copy:

func process(_ data: sending Data) async throws {
    // data ownership chuyển sang task này — không share với caller
}

Migrate từ Swift 5 sang Swift 6

Bước 1: Bật SWIFT_STRICT_CONCURRENCY = complete trong Xcode build settings. Xcode sẽ highlight toàn bộ Sendable violations.

Bước 2: Fix từng module:

  • Struct/enum: thêm : Sendable nếu properties đều Sendable.
  • Class mutable: convert sang actor hoặc @MainActor.
  • Closure capture: replace mutable captured variable bằng actor hoặc sending parameter.

Bước 3: Chạy swift migrate tool — tool tự apply nhiều fix phổ biến (thêm Sendable, chuyển class sang actor).

Bước 4: Test kỹ — đặc biệt là unit test chạy parallel, memory test bằng Thread Sanitizer.

Khi nào cần quan tâm

Swift 6 concurrency không chỉ cho app mới — nó còn quan trọng khi bạn:

  • Viết library/framework — consumer sẽ compile với strict checking, nên bạn phải expose Sendable types.
  • Maintain legacy codebase — migrate từ Swift 5 dần, tránh tech debt.
  • Build real-time app — audio processing, video streaming, game loop — data race là kẻ thù số 1.

Tài liệu tham khảo

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

Sơ đồ workflow migrate và sqlc trong CI/CD pipeline

Database migration an toàn với Golang golang-migrate sqlc Atlas so sánh

Tại sao cần database migration tool trong Golang Khi xây dựng ứng dụng production với Golang, việc quản lý thay đổi schema database là bài toán bắt buộc. Ba công…

Xem thêm

TypeScript 5.5: type inference cải tiến, decorators chuẩn hóa, isolated declarations

Giới thiệu TypeScript 5.5 mang đến những cải tiến đáng kể cho hệ thống kiểu dữ liệu, khiến mã nguồn an toàn hơn và dễ bảo trì hơn. Bài viết…

Xem thêm

So sánh các web framework Python phổ biến

Giới thiệu So sánh các web framework Python phổ biến là một trong những chủ đề nóng nhất trong thời gian qua. Bài viết này sẽ cung cấp một cái…

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