美文网首页Swift Async/Await
理解 Swift Actor 隔离关键字:nonisolated

理解 Swift Actor 隔离关键字:nonisolated

作者: 韦弦Zhy | 来源:发表于2022-11-02 08:46 被阅读0次

    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

    相关文章

      网友评论

        本文标题:理解 Swift Actor 隔离关键字:nonisolated

        本文链接:https://www.haomeiwen.com/subject/uofozrtx.html