Swift 目前不支持泛型类型上的静态存储属性的原因是,泛型占位符的每个特化都需要单独的属性存储 - 对此in this Q&A 进行了更多讨论。
但是,我们可以自己使用全局字典来实现这一点(请记住,静态属性只不过是命名为给定类型的全局属性)。不过,在此过程中需要克服一些障碍。
第一个障碍是我们需要一个键类型。理想情况下,这将是该类型的通用占位符的元类型值;但是元类型目前不能符合协议,因此不是Hashable。要解决这个问题,we can build a wrapper:
/// Hashable wrapper for any metatype value.
struct AnyHashableMetatype : Hashable {
static func ==(lhs: AnyHashableMetatype, rhs: AnyHashableMetatype) -> Bool {
return lhs.base == rhs.base
}
let base: Any.Type
init(_ base: Any.Type) {
self.base = base
}
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(base))
}
// Pre Swift 4.2:
// var hashValue: Int { return ObjectIdentifier(base).hashValue }
}
第二个是字典的每个值可以是不同的类型;幸运的是,只需擦除到Any 并在需要时回滚即可轻松解决。
这就是它的样子:
protocol Entity {
associatedtype PrimaryKey
}
struct Foo : Entity {
typealias PrimaryKey = String
}
struct Bar : Entity {
typealias PrimaryKey = Int
}
// Make sure this is in a seperate file along with EntityCollection in order to
// maintain the invariant that the metatype used for the key describes the
// element type of the array value.
fileprivate var _loadedEntities = [AnyHashableMetatype: Any]()
struct EntityCollection<T : Entity> {
static var loadedEntities: [T] {
get {
return _loadedEntities[AnyHashableMetatype(T.self), default: []] as! [T]
}
set {
_loadedEntities[AnyHashableMetatype(T.self)] = newValue
}
}
// ...
}
EntityCollection<Foo>.loadedEntities += [Foo(), Foo()]
EntityCollection<Bar>.loadedEntities.append(Bar())
print(EntityCollection<Foo>.loadedEntities) // [Foo(), Foo()]
print(EntityCollection<Bar>.loadedEntities) // [Bar()]
我们能够通过loadedEntities的实现来保持用于键的元类型描述数组值的元素类型的不变性,因为我们只为T.self键存储[T]值。
这里有一个潜在的性能问题,但是使用 getter 和 setter;数组值会因突变而受到复制(突变调用 getter 以获取临时数组,该数组发生突变,然后调用 setter)。
(希望我们很快就能得到通用地址...)
根据这是否是性能问题,您可以实现一个静态方法来执行数组值的就地突变:
func with<T, R>(
_ value: inout T, _ mutations: (inout T) throws -> R
) rethrows -> R {
return try mutations(&value)
}
extension EntityCollection {
static func withLoadedEntities<R>(
_ body: (inout [T]) throws -> R
) rethrows -> R {
return try with(&_loadedEntities) { dict -> R in
let key = AnyHashableMetatype(T.self)
var entities = (dict.removeValue(forKey: key) ?? []) as! [T]
defer {
dict.updateValue(entities, forKey: key)
}
return try body(&entities)
}
}
}
EntityCollection<Foo>.withLoadedEntities { entities in
entities += [Foo(), Foo()] // in-place mutation of the array
}
这里发生了很多事情,让我们解压一下:
- 我们首先从字典中删除该数组(如果存在)。
- 然后我们将突变应用到阵列。由于它现在被唯一引用(不再出现在字典中),因此可以就地变异。
- 然后我们将变异的数组放回字典中(使用
defer,这样我们就可以巧妙地从body 返回,然后再放回数组)。
我们在这里使用with(_:_:) 是为了确保我们在整个withLoadedEntities(_:) 中拥有对_loadedEntities 的写访问权限,以确保Swift 捕获这样的独占访问违规:
EntityCollection<Foo>.withLoadedEntities { entities in
entities += [Foo(), Foo()]
EntityCollection<Foo>.withLoadedEntities { print($0) } // crash!
}