我正在尝试在类上添加参与者隔离,但它实现了自定义 Hashable(因此平等)。
可能吗? 这是一个简化的示例:
actor SomeFoo: Equatable {
var someValue: Double = 0
public static func ==(lhs: SomeFoo, rhs: SomeFoo) -> Bool {
fabs(lhs.someValue - rhs.someValue) < 0.0001
}
}
在我的搜索中,我发现您可以为自我声明的演员执行此操作(符合协议中的演员,如果您具有不可变状态,这似乎可以工作)。
但在这种情况下,我需要 Equatable 来符合,并且由于我正在尝试开始的实际代码中状态的性质,我需要基于可变状态的相等 (==) 的自定义实现。
通常,如果您试图以
===
以外的方式使 actor 符合 Equatable,那么您可能会滥用 actor 或 Equatable。在您的具体示例中,这应该是值类型(结构)。参与者平等的概念,特别是依赖于可变状态,是非常有问题的,并且会产生一系列竞争条件。在您测试 ==
的时间和您使用该事实的时间之间,任何一个参与者都可能发生了变化,以致他们不再相等。几乎任何你想做的事情都应该以另一种方式完成。
在继续之前,请确保您需要 Equatable。特别是,请确保您的类型符合 Equatable 的可替代性语义:
平等意味着可替代性——任何两个同等比较的实例都可以在任何依赖于它们的值的代码中互换使用。为了保持可替换性,== 运算符应考虑 Equatable 类型的所有可见方面。不鼓励公开 Equatable 类型除类标识之外的非值方面,任何公开的内容都应在文档中明确指出。
这通常是演员成为平等者毫无意义的原因。在所有情况下,当他们的
someValue
值甚至不完全相同时,你真的可以用其中一个演员替换另一个吗?
考虑到所有这些,仍然值得讨论如何将其作为练习,即使它几乎肯定没有用。
第一种方法是让 actor 不可变。如果
someValue
是 let
而不是 var
,那么一切都会正常工作。
当然,如果
someValue
是let
,这可能就不是演员了,所以让我们继续讨论硬案例。符合两个可变参与者的 Equatable。
为此,您必须拍摄演员的快照。这是我通常使用的模式:
import os
actor SomeFoo: Equatable {
struct State {
var someValue: Double = 0
}
private let state = OSAllocatedUnfairLock(initialState: State())
nonisolated var snapshot: State {
get { state.withLock { $0 } }
set { state.withLock { $0 = newValue } }
}
public static func ==(lhs: SomeFoo, rhs: SomeFoo) -> Bool {
fabs(lhs.snapshot.someValue - rhs.snapshot.someValue) < 0.0001
}
}
您会很想创建一个
nonisolated var someValue
计算的 getter。创建它时要非常非常小心。如果有多个可变属性(通常有),这会产生竞争条件,其中多个提取可能不同步。例如,假设你写了这个:
// This is a very dangerous Actor.
actor SomeFoo {
struct State {
var firstName: String = ""
var lastName: String = ""
}
private let state = OSAllocatedUnfairLock(initialState: State())
nonisolated var snapshot: State {
get { state.withLock { $0 } }
set { state.withLock { $0 = newValue } }
}
// These are terrible ideas; don't do this.
nonisolated var firstName: String {
get { snapshot.firstName }
set { snapshot.firstName = newValue }
}
nonisolated var lastName: String {
get { snapshot.lastName }
set { snapshot.lastName = newValue }
}
}
这是每个人都认为自己想要的,但却是在乞求创造竞争条件。想象有东西正在阅读:
if obj.firstName == "John" && obj.lastName == "Doe" { ... }
同时还有其他东西在写:
obj.firstName = "John"
obj.lastName = "Smith"
这些可能会交织并产生竞争条件,这正是您使用 Actor 来避免的。在设计你的 Actor 时,你需要非常仔细地考虑哪些值是原子的。
可变引用类型通常不应该是 Equatable。