Swift 并发的终局形态

Swift 从 5.5 引入 async/await 与 Actor,到 6.0 引入完整的数据隔离检查(Complete Concurrency Checking),再到 6.2 的一系列易用性改进,目标始终清晰:让数据竞争在编译期被消灭,而不是靠运行时排查。

Swift 6 语言模式下,跨 actor 传递非 Sendable 类型会直接编译报错。这曾让迁移痛苦,6.2 则大幅降低了心智负担。

一、async/await 与结构化并发

import Foundation

struct Repo: Decodable { let name: String; let stars: Int }

func fetchRepos(user: String) async throws -> [Repo] {
    let url = URL(string: "https://api.github.com/users/\(user)/repos")!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
        throw URLError(.badServerResponse)
    }
    return try JSONDecoder().decode([Repo].self, from: data)
}

// 结构化并发:任务组内的子任务生命周期受父任务约束
func loadDashboard() async throws -> (repos: [Repo], avatar: Data) {
    try await withThrowingTaskGroup(of: Any?.self) { group in
        group.addTask { try await fetchRepos(user: "apple") }
        group.addTask { try await fetchAvatar(user: "apple") }

        var repos: [Repo] = []
        var avatar = Data()
        for try await result in group {
            if let r = result as? [Repo] { repos = r }
            if let a = result as? Data { avatar = a }
        }
        return (repos, avatar)
    }
}

任务组保证:父任务取消时,所有子任务自动取消,不会出现泄漏的后台任务。

二、Actor 与 Sendable

// Actor 保证内部状态串行访问
actor Counter {
    private var value = 0

    func increment() -> Int {
        value += 1
        return value
    }
}

// 跨 actor 传递必须 Sendable
struct User: Sendable {
    let id: Int
    let name: String
}

@MainActor
final class ViewModel: ObservableObject {
    @Published var repos: [Repo] = []

    func load() async {
        // await 跨越 actor 边界,编译器强制插入挂起点
        repos = (try? await fetchRepos(user: "apple")) ?? []
    }
}

Swift 6.2 的重要改进是 @concurrent 与默认的 nonisolated 行为调整:以前 nonisolated 异步函数会跳到后台执行器,导致大量意外的 actor 跳跃;6.2 起默认保持在调用方上下文,只有显式标注 @concurrent 才真正切到后台。

final class ImageLoader {
    // 显式声明:这个耗时操作应该在后台并发执行
    @concurrent
    func decode(_ data: Data) async -> Image {
        // CPU 密集解码
        return Image(data: data)!
    }
}

三、服务端 Swift:Vapor 实战

// Package.swift 依赖
// .package(url: "https://github.com/vapor/vapor.git", from: "4.100.0")

import Vapor

func routes(_ app: Application) throws {
    app.get("health") { _ in ["ok": true] }

    app.post("users") { req async throws -> User in
        let input = try req.content.decode(CreateUser.self)
        guard input.email.contains("@") else {
            throw Abort(.badRequest, reason: "邮箱格式不正确")
        }
        return try await UserStore.create(input)
    }

    // 流式响应
    app.get("stream") { req -> Response in
        let body = Response.Body(stream: { writer in
            for i in 1...10 {
                try await writer.write(.buffer(.init(string: "chunk \(i)\n")))
            }
            try await writer.write(.end)
        })
        return Response(status: .ok, body: body)
    }
}

Swift on Server 的优势在于:

  • 内存占用远低于 JVM/Node,容器镜像可小到几十 MB;
  • 强类型 + 编译期并发检查,线上崩溃率显著更低;
  • 与 iOS 端共享模型层代码。

四、与 C/C++、Rust 互操作

// 直接调用 C 库,无需桥接文件之外的额外成本
import Foundation

func sha256(_ text: String) -> [UInt8] {
    var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    text.withCString { ptr in
        CC_SHA256(UnsafeRawPointer(ptr), CC_LONG(strlen(ptr)), &hash)
    }
    return hash
}

Swift 6 起对 C++ 互操作、以及通过 Swift 调用 Rust(借助 swift-bridge / uniffi 生成绑定)的支持明显增强,这让 Swift 在“渐进替换老代码”的场景中更实用。

五、迁移到 Swift 6 语言模式

// 逐模块开启,而不是一次性全量切换
// Package.swift
swiftSettings: [
    .enableUpcomingFeature("StrictConcurrency"),
    .enableExperimentalFeature("NonisolatedNonSendingByDefault"),
]

步骤建议:

  1. 先把数据模型标注 Sendable;
  2. 把全局可变状态收进 actor 或加 @MainActor;
  3. 最后再打开 Swift 6 语言模式,逐个修复剩余警告。

小结

Swift 6.2 让严格并发从“处处报错”变得“默认合理”。编译期消灭数据竞争,是任何语言都值得追求的方向,而 Swift 目前走得最坚决。