我有一个具有同步功能要求的协议
class NonSendable {}
protocol P1 {
func doSomething(_ nonSendable: NonSendable) async
}
当我使用
actor
遵守此协议时,同时启用快速并发检查,我会收到警告
actor P1Actor: P1 {
// Warning: Non-sendable type 'NonSendable?' returned by actor-isolated instance method 'doSomething' satisfying protocol requirement cannot cross actor boundary
func doSomething(_ nonSendable: NonSendable) {}
}
但是当我将
actor
更改为 struct
或 class
时,警告就会消失。
struct P1Struct: P1 {
// No warning
func doSomething(_ nonSendable: NonSendable) async {}
}
我不明白为什么我会收到演员的警告。我很困惑,因为在所有情况下
doSomething
都会从异步上下文触发,那么当函数隔离到 actor
时,为什么参数需要是可发送的?
回想一下,非
Sendable
类型的值不能跨越参与者边界。
当您从非隔离上下文调用
doSomething
时,这正是传递给 doSomething
的参数将要做的事情 - 被发送到 P1Actor
。您肯定会同意这违反了Sendable
的规则:
class Foo {
let x = NonSendable()
func foo() async {
let p1 = P1Actor()
// x is not Sendable but its being sent to p1!
await p1.doSomething(x) // error here!
}
}
如果
doSomething
不满足任何协议要求(例如,如果 P1Actor
不符合 P1
),Swift 仍然允许您声明此方法,因为它可以在调用站点发出错误(如上面示例)。
但是,如果
doSomething
是协议要求,则 Swift 无法再在调用站点判断对 doSomething
的调用是否会将不可发送的值发送给另一个参与者。考虑:
class Foo {
let x = NonSendable()
func foo(p: any P1) async {
// is x being sent across actor boundaries? It depends on whether p is an actor!
await p.doSomething(x)
}
}
在编译时,不知道
p
是否是一个 actor,因此 Swift 必须首先禁止你声明 doSomething
。
解决此问题的一种方法是让协议见证
nonisolated
。让它从不可发送的参数值中提取可发送的内容,然后调用实际的隔离实现。举个例子:
actor P1Actor: P1 {
nonisolated func doSomething(_ nonSendable: NonSendable) async {
someIsolatedImplementation(nonSendable.someString, nonSendable.someNumber, nonSendable.someFlag)
}
// the parameters of this are all sendable!
func someIsolatedImplementation(_ a: String, _ b: Int, _ c: Bool) {
}
}