理解 Swift Actor 隔離關(guān)鍵字:nonisolated 和 isolated

SE-313 引入了非隔離(nonisolated)和隔離(isolated)關(guān)鍵字作為添加 Actor 隔離控制的一部分。 Actor 是一種使用新并發(fā)框架為共享可變狀態(tài)提供同步的新方法。

如果您不熟悉 Swift 中的 Actor,我鼓勵(lì)您閱讀我的文章Swift中的Actors 使用以如何及防止數(shù)據(jù)競(jìng)爭(zhēng),文章內(nèi)詳細(xì)描述了它。本文將解釋在 Swift 中使用 Actor 時(shí)如何控制方法和參數(shù)的隔離。

了解Actor的默認(rèn)行為

默認(rèn)情況下,actor 的每個(gè)方法都是隔離的,這意味著您必須已經(jīng)在 actor 的上下文中,或者使用 await 等待批準(zhǔn)訪問 actor 包含的數(shù)據(jù)。

您可以在我的文章 Swift 中的async/await ——代碼實(shí)例詳解了解有關(guān) async/await 的更多信息。

通常我們使用Actor會(huì)遇到以下錯(cuò)誤:

  • Actor-isolated property ‘balance’ can not be referenced from a non-isolated context
  • Expression is ‘a(chǎn)sync’ but is not marked with ‘a(chǎn)wait’

這兩個(gè)錯(cuò)誤都有相同的根本原因:Actor 隔離對(duì)其屬性的訪問以確?;コ庠L問。

以如下銀行賬戶 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 方法默認(rèn)是隔離的,但沒有明確標(biāo)記為隔離。您可以將此與默認(rèn)情況下為內(nèi)部但未使用 internal 關(guān)鍵字標(biāo)記的方法進(jìn)行比較。實(shí)際上真實(shí)代碼大概如下所示:

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
}

但是,像這個(gè)例子一樣使用隔離關(guān)鍵字(isolated)顯式標(biāo)記方法將導(dǎo)致以下錯(cuò)誤:

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

我們只能在參數(shù)聲明中使用隔離關(guān)鍵字。

將 Actor 參數(shù)標(biāo)記為隔離

對(duì)參數(shù)使用隔離關(guān)鍵字可以很好地使用更少的代碼來解決特定問題。上面的代碼示例介紹了一個(gè)deposit方法來更改另一個(gè)銀行賬戶的余額:

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

結(jié)果是使用更少的代碼同時(shí)可能使您的代碼更易于閱讀。

編譯器目前禁止但允許使用多個(gè)隔離參數(shù):

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

不過,最初的提議表明這是不允許的,因此未來的 Swift 版本可能會(huì)要求您更新此代碼。

在 Actor 中使用 nonisolated 關(guān)鍵字

將方法或?qū)傩詷?biāo)記為非隔離可用于選擇退出Actor的默認(rèn)隔離。在訪問不可變值或符合協(xié)議要求時(shí),選擇退出可能會(huì)有所幫助。

在以下示例中,我們?yōu)锳ctor添加了一個(gè)帳戶持有人姓名:

actor BankAccountActor {
    
    let accountHolder: String

    // ...
}

帳戶持有人是不可變的,因此可以安全地從非隔離環(huán)境訪問。編譯器足夠聰明,可以識(shí)別這種狀態(tài),因此無需顯式將此參數(shù)標(biāo)記為非隔離。

但是,如果我們引入計(jì)算屬性訪問不可變屬性,我們必須幫助編譯器識(shí)別這一點(diǎn)。讓我們看一下下面的例子:

actor BankAccountActor {

    let accountHolder: String
    let bank: String

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

    // ...
}

如果我們現(xiàn)在要打印出detail,我們會(huì)遇到以下錯(cuò)誤:

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

bankaccountHolder 都是不可變屬性,因此我們可以顯式地將計(jì)算屬性標(biāo)記為nonisolated然后便可以解決錯(cuò)誤:

actor BankAccountActor {

    let accountHolder: String
    let bank: String

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

    // ...
}

使用非隔離解決協(xié)議一致性

同樣的原則也適用于添加協(xié)議一致性,在這種一致性中,您確定只能訪問不可變狀態(tài)。例如,我們可以用更好的 CustomStringConvertible 協(xié)議替換 details 屬性:

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

使用 Xcode 推薦的默認(rèn)實(shí)現(xiàn),我們會(huì)遇到以下錯(cuò)誤:

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

我們可以再次通過使用 nonisolated 關(guān)鍵字解決這個(gè)問題:

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

如果我們?cè)诜歉綦x環(huán)境中意外訪問了隔離屬性,編譯器將足夠聰明地警告我們:

從非隔離環(huán)境訪問隔離屬性將導(dǎo)致編譯器錯(cuò)誤。

從非隔離環(huán)境訪問隔離屬性將導(dǎo)致編譯器錯(cuò)誤。

繼續(xù)您的 Swift 并發(fā)之旅

并發(fā)更改不僅僅是 async-await,還包括許多您可以在代碼中受益的新功能。所以當(dāng)你在做的時(shí)候,為什么不深入研究其他并發(fā)特性呢?

結(jié)論

Swift 中的 Actor 是同步訪問共享可變狀態(tài)的好方法。然而,在某些情況下,我們希望控 Actor 隔離,因?yàn)槲覀兛赡艽_定只訪問不可變狀態(tài)。通過使用非隔離(nonisolated)和隔離(isolated)關(guān)鍵字,我們可以精確控制Actor的隔離狀態(tài)。

轉(zhuǎn)自 Nonisolated and isolated keywords: Understanding Actor isolation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容