协议继承中出现的意外行为
我在项目设置中默认使用主actor,并使用Swift 6模式。下面是代码:
public protocol Main {}
nonisolated public protocol NonIso {}
// Foo is on main
// As proven by the compilation failure in `testNonIso()`
public protocol Foo: NonIso, Main {
func foo()
}
nonisolated func testNonIso() {
let f: Foo! = nil
// Call to main actor-isolated instance method 'foo()' in a synchronous nonisolated context
f.foo()
}
正如我的 testNonIso() 函数所验证的,Foo 协议被限定在主actor上。
接着我把 Foo 标记为非隔离,错误就消失了:
public protocol Main {}
nonisolated public protocol NonIso {}
// Foo is on nonisolated
// As proven by no compilation error in `testNonIso()`
nonisolated public protocol Foo: NonIso, Main {
func foo()
}
nonisolated func testNonIso() {
let f: Foo! = nil
// this compiles fine, since Foo is non-isolated
f.foo()
}
以上都说得通。
接下来情况会有点怪——不是使用 NonIso 协议,而是使用 Codable 协议,它也是非隔离的:
public protocol Main {}
// Now `Foo` becomes non-isolated for some reason. In the previous example Foo was main isolated.
// This is verified by the compilation error when conforming struct `F` to `Foo`
public protocol Foo: Codable, Main {
func foo()
}
// Conformance of 'F' to protocol 'Foo' crosses into main actor-isolated code and can cause data races
struct F: Foo {
func foo() {}
}
上面的代码给出编译错误,表明这次 Foo 变成了非隔离。
现在把 Foo 标记为 @MainActor:
public protocol Main {}
nonisolated public protocol NonIso {}
// Still non-isolated, despite of @MainActor here
@MainActor public protocol Foo: Codable, Main {
func foo()
}
// Conformance of 'F' to protocol 'Foo' crosses into main actor-isolated code and can cause data races
struct F: Foo {
func foo() {}
}
我得到完全相同的错误,意味着即便我把 Foo 标记为主actor,它仍然是非隔离的。
现在再做一次确认,开个玩笑,把上面的代码中的 Codable 重新替换回 NonIso,编译错误就消失了:
public protocol Main {}
nonisolated public protocol NonIso {}
// Foo is main isolated, with or without @MainActor
@MainActor public protocol Foo: NonIso, Main {
func foo()
}
// Compiles fine this time
struct F: Foo {
func foo() {}
}
我想知道我的 NonIso 协议与 Codable 协议之间有什么区别导致这种行为?
我在使用Xcode 26.2
解决方案
将 Foo 改为从 Codable 继承之后,你说
上面的代码给出编译错误,表明这次
Foo变成了非隔离。
这不是真的。Foo 仍然是主actor隔离的。编译器错误涉及的是 Codable 的要求,它们是非隔离的。如果 Foo 真的是非隔离的,我不应该能够用 @MainActor 方法来满足它的 foo() 要求,但我确实可以!
// Just to make it very clear that this is not due to an isolated conformance to Foo,
// we write 'nonisolated Foo' explicitly to disable any inference of isolated conformance
struct F: nonisolated Foo {
// this compiles!
@MainActor
func foo() {}
// these are required to be non-isolated by Codable
nonisolated init(from decoder: any Decoder) throws {
}
nonisolated func encode(to encoder: any Encoder) throws {
}
}
毕竟,@MainActor 协议仍然可以有非隔离的要求。Foo 实际上只有一个主actor隔离的要求 foo,以及两个非隔离的要求 init(from:) 和 encode(to:)。
与其把 protocol Foo: Codable, Main 读作“Foo 继承自 Codable 和 Main”,不如把它理解为“实现 Foo 还需要同时实现 Codable 和 Main”。
之所以 NonIso 情况能编译,是因为 NonIso 没有任何要求,因此 F 对它是符合的(因此也符合 Foo),即使 F 是主actor隔离的。