前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解 Swift Actor 隔离关键字:nonisolated 和 isolated

理解 Swift Actor 隔离关键字:nonisolated 和 isolated

作者头像
韦弦zhy
发布2022-11-14 15:23:39
8120
发布2022-11-14 15:23:39
举报

SE-313 引入了非隔离(nonisolated)和隔离(isolated)关键字作为添加 Actor 隔离控制的一部分。 Actor 是一种使用新并发框架为共享可变状态提供同步的新方法。

如果您不熟悉 Swift 中的 Actor,我鼓励您阅读我的文章Swift中的Actors 使用以如何及防止数据竞争,文章内详细描述了它。本文将解释在 Swift 中使用 Actor 时如何控制方法和参数的隔离。

了解Actor的默认行为

默认情况下,actor 的每个方法都是隔离的,这意味着您必须已经在 actor 的上下文中,或者使用 await 等待批准访问 actor 包含的数据。

您可以在我的文章 Swift 中的async/await ——代码实例详解了解有关 async/await 的更多信息。

通常我们使用Actor会遇到以下错误:

  • Actor-isolated property ‘balance’ can not be referenced from a non-isolated context
  • Expression is ‘async’ but is not marked with ‘await’

这两个错误都有相同的根本原因:Actor 隔离对其属性的访问以确保互斥访问。

以如下银行账户 Actor 为例:

actor BankAccountActor {
    enum BankError: Error {
        case insufficientFunds
    }
    
    var balance: Double
    
    init(initialDeposit: Double) {
        self.balance = initialDeposit
    }
    
    func transfer(amount: Double, to toAccount: BankAccountActor) async throws {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
        await toAccount.deposit(amount: amount)
    }
    
    func deposit(amount: Double) {
        balance = balance + amount
    }
}

Actor 方法默认是隔离的,但没有明确标记为隔离。您可以将此与默认情况下为内部但未使用 internal 关键字标记的方法进行比较。实际上真实代码大概如下所示:

isolated func transfer(amount: Double, to toAccount: BankAccountActor) async throws {
    guard balance >= amount else {
        throw BankError.insufficientFunds
    }
    balance -= amount
    await toAccount.deposit(amount: amount)
}

isolated func deposit(amount: Double) {
    balance = balance + amount
}

但是,像这个例子一样使用隔离关键字(isolated)显式标记方法将导致以下错误:

‘isolated’ may only be used on ‘parameter’ declarations

我们只能在参数声明中使用隔离关键字。

将 Actor 参数标记为隔离

对参数使用隔离关键字可以很好地使用更少的代码来解决特定问题。上面的代码示例介绍了一个deposit方法来更改另一个银行账户的余额:

func transfer(amount: Double, to toAccount: isolated BankAccountActor) async throws {
    guard balance >= amount else {
        throw BankError.insufficientFunds
    }
    balance -= amount
    toAccount.balance += amount
}

结果是使用更少的代码同时可能使您的代码更易于阅读。

编译器目前禁止但允许使用多个隔离参数:

func transfer(amount: Double, from fromAccount: isolated BankAccountActor, to toAccount: isolated BankAccountActor) async throws {
    // ..
}

不过,最初的提议表明这是不允许的,因此未来的 Swift 版本可能会要求您更新此代码。

在 Actor 中使用 nonisolated 关键字

将方法或属性标记为非隔离可用于选择退出Actor的默认隔离。在访问不可变值或符合协议要求时,选择退出可能会有所帮助。

在以下示例中,我们为Actor添加了一个帐户持有人姓名:

actor BankAccountActor {
    
    let accountHolder: String

    // ...
}

帐户持有人是不可变的,因此可以安全地从非隔离环境访问。编译器足够聪明,可以识别这种状态,因此无需显式将此参数标记为非隔离。

但是,如果我们引入计算属性访问不可变属性,我们必须帮助编译器识别这一点。让我们看一下下面的例子:

actor BankAccountActor {

    let accountHolder: String
    let bank: String

    var details: String {
        "Bank: \(bank) - Account holder: \(accountHolder)"
    }

    // ...
}

如果我们现在要打印出detail,我们会遇到以下错误:

Actor-isolated property ‘details’ can not be referenced from a non-isolated context

bankaccountHolder 都是不可变属性,因此我们可以显式地将计算属性标记为nonisolated然后便可以解决错误:

actor BankAccountActor {

    let accountHolder: String
    let bank: String

    nonisolated var details: String {
        "Bank: \(bank) - Account holder: \(accountHolder)"
    }

    // ...
}

使用非隔离解决协议一致性

同样的原则也适用于添加协议一致性,在这种一致性中,您确定只能访问不可变状态。例如,我们可以用更好的 CustomStringConvertible 协议替换 details 属性:

extension BankAccountActor: CustomStringConvertible {
    var description: String {
        "Bank: \(bank) - Account holder: \(accountHolder)"
    }
}

使用 Xcode 推荐的默认实现,我们会遇到以下错误:

Actor-isolated property ‘description’ cannot be used to satisfy a protocol requirement

我们可以再次通过使用 nonisolated 关键字解决这个问题:

extension BankAccountActor: CustomStringConvertible {
    nonisolated var description: String {
        "Bank: \(bank) - Account holder: \(accountHolder)"
    }
}

如果我们在非隔离环境中意外访问了隔离属性,编译器将足够聪明地警告我们:

从非隔离环境访问隔离属性将导致编译器错误。

从非隔离环境访问隔离属性将导致编译器错误。

继续您的 Swift 并发之旅

并发更改不仅仅是 async-await,还包括许多您可以在代码中受益的新功能。所以当你在做的时候,为什么不深入研究其他并发特性呢?

结论

Swift 中的 Actor 是同步访问共享可变状态的好方法。然而,在某些情况下,我们希望控 Actor 隔离,因为我们可能确定只访问不可变状态。通过使用非隔离(nonisolated)和隔离(isolated)关键字,我们可以精确控制Actor的隔离状态。

转自 Nonisolated and isolated keywords: Understanding Actor isolation

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 了解Actor的默认行为
  • 将 Actor 参数标记为隔离
  • 在 Actor 中使用 nonisolated 关键字
  • 使用非隔离解决协议一致性
  • 继续您的 Swift 并发之旅
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档