一般来说,如果你试图以===
以外的方式使一个演员符合Equatable,你很可能滥用了演员或Equatable.在您的特定示例中,这应该是一个值类型( struct ).行动者平等的概念,特别是依赖于可变状态,是非常有问题的,并产生了一系列的种族条件.在你测试==
的时间和你使用这个事实的时间之间,任何一个参与者可能已经改变,使他们不再平等.几乎任何你想用它做的事情都应该用另一种方式来做.
在继续之前,请确保您想要Equatable.特别是,确保您的类型符合substitutability semantics of Equatable:
相等意味着可替换性——任何两个比较相等的实例可以在任何依赖于它们值的代码中互换使用.为了保持可替换性,==操作符应该考虑Equatable类型的所有可见方面.不鼓励公开Equatable类型的非值方面,而不是类标识,任何公开的方面都应该在文档中明确指出.
这通常是一个原因,它是没有意义的演员平等.你真的能,在所有情况下,用其中一个演员代替另一个演员,当他们的someValue
甚至不是完全相同的值时?
考虑到所有这些,仍然值得讨论如何将其作为一个练习,即使它几乎肯定没有用处.
第一种方法是使演员不可改变.如果someValue
是let
,而不是var
,那么一切都很好.
当然,如果someValue
是let
,这可能不会是一个演员,所以让我们转移到困难的情况.对于两个可变的参与者来说,符合等价的.
要做到这一点,你必须拍摄演员的快照.这是我通常用途:
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来避免的.在设计你的演员时,你需要非常仔细地考虑哪些值是原子的.
可变引用类型通常不应该是Equatable.