Actor — 什么是它,数据隔离和并发性

作者: IT Sectr 发布日期: 2026-06-18 阅读时间: 11 分钟

Actor — 是 Swift 5.5+ 中引入的一种类型,它在语言层面解决了数据竞态问题。与手动锁和 DispatchQueue 队列不同,actor 自动隔离其状态并同步对它的访问。这意味着两个线程不能同时修改同一个 actor 类型的属性,从而无需开发者额外努力就排除了竞态条件。该机制基于 actor 隔离的概念,编译器跟踪对 actor 属性和方法的访问。据 Apple,2025 报道,actor 模型是 Swift 并发系统的基本组成部分。

主要内容

  • Actor — 在编译器层面自动将其状态与数据竞态隔离的类型
  • 隔离 保证只有一个线程同时访问 actor 实例
  • 调用 actor 方法通过 async/await 机制异步执行
  • Nonisolated 允许选择性关闭单个 actor 成员的隔离
  • Reentrancy 允许重新进入 actor,防止互斥锁死

Swift 中的 Actor 是什么?

Actor — 是一种引用类型,类似于类,但具有自动的竞态保护。它是在 Swift 5.5 中作为并发系统的一部分,与 async/await 和 Task 一起引入的。Actor 保证其可变状态始终不会在没有显式同步的情况下从不同线程同时被读取或写入。

Actor 声明语法

声明 actor 使用关键字 actor,后跟包含其成员的花括号。从语法上看,actor 类似于类,但行为或彻底不同。

swift
actor BankAccount {
    private var balance: Double
    
    init(initialBalance: Double) {
        self.balance = initialBalance
    }
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func getBalance() -> Double {
        return balance
    }
}

编译器自动隔离 actor 的所有属性和方法,使它们只能在 actor 上下文内访问。从 actor 外部尝试访问 balance 将会导致编译器报错,除非调用被标记为 async。

Actor 中的数据隔离如何工作

数据隔离 — 是 actor 的关键概念。Actor 通过 actor 执行器(actor executor)机制保证对其状态的互斥访问。每个 actor 都有自己的执行器,它顺序处理对其隔离成员的所有访问。

隔离的工作原理

当 actor 外部的代码调用其方法时,调用被放置在 actor 执行器的队列中。Actor 每次只执行一个任务,从而保证不会发生竞态。如果两个线程同时调用 deposit,第二个调用将等待第一个完成。

swift
let account = BankAccount(initialBalance: 1000.0)
// 异步调用 — 从 actor 外部需要
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()

每次调用 actor 方法都需要 await,因为 actor 可能正在处理其他任务。这不是 bug,而是一种有意识的设计,用于防止竞态。Swift 隐式地将 actor 属性的 getter 和 setter 变为 async,因此读取属性也需要 await。

Actor 与类的主要区别

Actor 和类都是引用类型,但它们在多线程环境中的行为截然不同。类不提供任何自动的竞态保护,而 actor 通过类型系统在编译器层面内置了这种保护。

特征ActorClass
竞态保护自动,在编译器层面需要手动同步
属性访问只能通过 await 从外部直接,无同步
继承只能从其他 actor标准类继承
协议兼容可以遵循协议标准
性能隔离时开销低无同步 — 更快

Actor 的继承

Actor 只能从另一个 actor 继承,不能从类继承。这是有意为之的,因为类没有 actor 隔离机制,混合两种模型将导致未定义行为。

swift
actor SavingsAccount: BankAccount {
    func applyInterest(rate: Double) {
        let interest = balance * rate
        balance += interest
    }
}

Actor 方法的异步调用

异步调用 — 是从外部代码与 actor 互动的机制。由于 actor 隔离其状态,从外部访问其成员都需要 await。这使得 Swift 能够保证调用代码不会阻塞线程,并且 actor 可以处理其他请求。

全局和本地 actor

除了声明的 actor 类型外,Swift 还支持 全局 actor — @MainActor 属性,它将类、属性或方法标记为在主线程上执行。这在使用 UIKit 和 SwiftUI 时尤其有用。

swift
@MainActor
class ViewModel: ObservableObject {
    @Published var title: String = ""
    
    func updateTitle() {
        // 该代码保证在主线程上运行
        title = "New Title"
    }
}

使用 @MainActor 消除了手动调用 DispatchQueue.main.async 的必要,使代码更清洁、更安全。编译器检查切换到主线程是否正确发生。

Nonisolated 和 actor-isolated 上下文

Nonisolated — 是一个关键字,允许将 actor 的方法或计算属性标记为非隔离的。这些成员无法访问 actor 的隔离状态,但可以从 actor 外部无需 await 就能调用。

Nonisolated 函数

Nonisolated 方法对于不依赖于 actor 可变状态的计算很有用。例如,formatBalance 方法不直接访问 balance,只是格式化传入的值 — 这样的方法可以安全地设为 nonisolated。

swift
actor BankAccount {
    private var balance: Double = 0
    
    nonisolated func formatBalance(amount: Double) -> String {
        return "$\(amount)"
    }
}

Nonisolated 成员同步执行,不需要 await。但它们不能直接读取 actor 的隔离属性。如果 nonisolated 方法需要来自 actor 的值,则必须将其 作为参数传递

Reentrancy 和重新进入

Reentrancy — 是一种机制,允许在等待异步调用时重新进入 actor。如果没有 reentrancy,当 actor 的一个方法等待另一个,而另一个又回过头等待第一个时,actor 可能会永久锁死。

Reentrancy 如何工作

当 actor 内部的代码执行 await 时,actor 暂停当前任务,并可以 处理另一个排队的任务。await 完成后,任务恢复执行。这防止了死锁,但需要谨慎: actor 在 await 点之间的状态可能会改变。

swift
actor DataProcessor {
    var cache: [Int: String] = [:]
    
    func process(id: Int) async -> String {
        if let cached = cache[id] {
            return cached
        }
        // await — 重新进入点
        let result = await fetchData(id: id)
        // 缓存可能在 await 之后发生了变化 — 请重新检查
        cache[id] = result
        return result
    }
}

开发者必须考虑 reentrancy,并在 await 点之后检查 actor 的状态。一个典型的错误是假设 actor 隔离在异步 暂停期间保持不变。实际上,在 await 和下一条语句之间,状态可能与预期的不同。

常见问题

Actor 与 Swift 中的类有什么区别?

Actor 自动将其状态与数据竞态隔离,从外部访问需要 await。类不提供这种保护 — 开发者自行负责通过锁或队列进行同步。Actor 只能从 actor 继承,类从类继承。

可以从 Actor 继承吗?

可以,actor 可以从另一个 actor 继承。子类继承父类的所有隔离属性和方法。Actor 不能从类继承,因为类在编译器层面没有 actor 隔离机制。

什么是 actor-isolated 上下文?

Actor-isolated 上下文 — 是允许直接访问 actor 可变状态的代码区域。在被标记为 isolated(默认)的 actor 方法内部,可以无需 await 就读写属性。编译器检查隔离边界。

如何将数据从 Actor 传递给外部代码?

来自 actor 的数据通过 async 方法(返回 Sendable 类型)或通过接受值作为参数的 nonisolated 方法传递。还可以创建一个 async 属性,它以 Sendable 结构的形式返回 actor 状态的快照。

Actor 支持协议继承吗?

可以,actor 可以遵循协议。如果协议包含 隔离要求(actor-isolated),它们将自动成为 actor-隔离的。对于协议中的异步方法,可以使用 isolated 标记指定它们应在特定的 actor 上调用。

总结

  • Actor — 引用类型,具有自动数据隔离功能,在 Swift 5.5+ 中引入
  • 隔离 通过 actor 执行器工作,顺序处理请求
  • 外部访问 actor 需要 await — 这保证了无竞态
  • Nonisolated 允许声明同步方法,无法访问隔离状态
  • 全局 actor (@MainActor) 将隔离扩展到整个执行线程
  • Reentrancy 通过允许在 await 期间重新进入 actor 来防止死锁

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读