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
bank 和 accountHolder 都是不可變屬性,因此我們可以顯式地將計(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ò)誤。
繼續(xù)您的 Swift 并發(fā)之旅
并發(fā)更改不僅僅是 async-await,還包括許多您可以在代碼中受益的新功能。所以當(dāng)你在做的時(shí)候,為什么不深入研究其他并發(fā)特性呢?
- Swift 中的 async/await
- Swift 中的 async let
- Swift 中的 Task
- Swift 中的 Actors 使用以如何及防止數(shù)據(jù)競(jìng)爭(zhēng)
- Swift 中的 MainActor 使用和主線程調(diào)度
- 理解 Swift Actor 隔離關(guān)鍵字:nonisolated 和 isolated
- Swift 中的 Sendable 和 @Sendable 閉包
- Swift 中的 AsyncThrowingStream 和 AsyncStream
- Swift 中的 AsyncSequence
結(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