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 let và TaskGroup 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

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