为什么不强制实现Sendable协议
在下面的代码中,我有 actor BankAccount 和 non-sendable 类型的 AlternativeBankAccount。两者的主体相同。
我不明白为什么在把 AlternativeBankAccount 传给 charge 函数时,编译器不会触发警告。异步上下文从 random 的主体切换到 charge,我原本以为这会强制 charge 的所有参数具备可发送性(sendable)。
如果有人能给我解释,我会非常高兴!提前感谢!
日志:
1 - <_NSMainThread: 0x600001704040>{number = 1, name = main}
2 - <NSThread: 0x600001711100>{number = 7, name = (null)}
代码:
actor BankAccount {
enum BankError: Error {
case insufficientFunds
}
var balance: Double
init(initialDeposit: Double) {
self.balance = initialDeposit
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
func deposit(amount: Double) {
balance += amount
}
}
class AlternativeBankAccount {
enum BankError: Error {
case insufficientFunds
}
var balance: Double
init(initialDeposit: Double) {
self.balance = initialDeposit
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
func deposit(amount: Double) {
balance += amount
}
}
struct Charger {
func charge(amount: Double, from bankAccount: isolated BankAccount, to otherAccount: AlternativeBankAccount)
async throws -> (Double, Double) {
print("2 - \(Thread.currentThread)")
try bankAccount.withdraw(amount: amount)
let newBalance = bankAccount.balance
otherAccount.deposit(amount: amount)
return (newBalance, otherAccount.balance)
}
}
@MainActor
class ViewModel {
let charger = Charger()
func random(bankAccount: BankAccount) {
let bankAccount2 = AlternativeBankAccount(initialDeposit: 200)
Task {
print("1 - \(Thread.currentThread)")
let aa = try? await charger.charge(amount: 100, from: bankAccount, to: bankAccount2)
print("balance", aa?.0 ?? "", aa?.1 ?? "")
}
}
}
解决方案
你在把 charge 传给之后,在其余的 Task 中根本没有访问过 bankAccount2。因此,编译器可以推断将其传递给 charge 是安全的,因为不会对 bankAccount2 进行任何同时访问(主actor在把它传给银行账户actor之后就再也不访问它)。
如果你说,
Task {
let aa = try? await charger.charge(amount: 100, from: bankAccount, to: bankAccount2) // error here
print("balance", aa?.0 ?? "", aa?.1 ?? "")
print(bankAccount2) // <---- added this access of bankAccount2
}
那么就会有错误。
或者如果把 bankAccount2 设为类的一个属性,而不是一个局部变量,这也会导致编译错误。编译器无法知道类属性被使用的所有位置。
这种编译器分析被称为 基于区域的隔离。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。