为什么不强制实现Sendable协议

移动开发 2026-07-11

在下面的代码中,我有 actor BankAccountnon-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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章