快速解决泛型类型的静态存储属性的最佳选择是什么? [英] What is a good alternative for static stored properties of generic types in swift?

查看:106
本文介绍了快速解决泛型类型的静态存储属性的最佳选择是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于尚未迅速为泛型类型支持静态存储属性,所以我想知道什么是不错的选择.

我的特定用例是我想快速构建一个ORM.我有一个Entity协议,该协议具有主键的关联类型,因为某些实体的id会具有整数,而某些实体的会具有字符串等.因此这使得Entity协议具有通用性.

现在,我还有一个EntityCollection<T: Entity>类型,它管理实体的集合,如您所见,它也是通用的. EntityCollection的目标是让您可以使用实体集合,就好像它们是普通数组一样,而不必知道背后有数据库. EntityCollection将负责查询和缓存,并尽可能进行优化.

我想在EntityCollection上使用静态属性来存储所有已经从数据库中获取的实体.这样,如果EntityCollection的两个单独实例要从数据库中获取同一实体,则该数据库将仅被查询一次.

你们知道我还能如何实现这一目标吗?

解决方案

Swift当前不支持泛型类型的静态存储属性的原因是,每个通用占位符的特殊化都需要单独的属性存储–在本问答中对此有更多讨论.

但是,我们可以使用全局字典自己实现这一点(请记住,静态属性不过是命名为给定类型的全局属性而已).不过,在执行此操作时要克服一些障碍.

第一个障碍是我们需要一个密钥类型.理想情况下,这将是类型的通用占位符的元类型值;但是,元类型当前不符合协议,因此不是Hashable.要解决此问题,我们可以构建包装器:

/// 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]值. /p>


但是,由于使用吸气剂和吸气剂,这里存在潜在的性能问题;数组值将因复制而遭受复制(mutation会调用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!
}

Since static stored properties are not (yet) supported for generic types in swift, I wonder what is a good alternative.

My specific use-case is that I want to build an ORM in swift. I have an Entity protocol which has an associatedtype for the primary key, since some entities will have an integer as their id and some will have a string etc. So that makes the Entity protocol generic.

Now I also have an EntityCollection<T: Entity> type, which manages collections of entities and as you can see it is also generic. The goal of EntityCollection is that it lets you use collections of entities as if they were normal arrays without having to be aware that there's a database behind it. EntityCollection will take care of querying and caching and being as optimized as possible.

I wanted to use static properties on the EntityCollection to store all the entities that have already been fetched from the database. So that if two separate instances of EntityCollection want to fetch the same entity from the database, the database will be queried only once.

Do you guys have any idea how else I could achieve that?

解决方案

The reason that Swift doesn't currently support static stored properties on generic types is that separate property storage would be required for each specialisation of the generic placeholder(s) – there's more discussion of this in this Q&A.

We can however implement this ourselves with a global dictionary (remember that static properties are nothing more than global properties namespaced to a given type). There are a few obstacles to overcome in doing this though.

The first obstacle is that we need a key type. Ideally this would be the metatype value for the generic placeholder(s) of the type; however metatypes can't currently conform to protocols, and so therefore aren't Hashable. To fix this, 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 }
}

The second is that each value of the dictionary can be a different type; fortunately that can be easily solved by just erasing to Any and casting back when we need to.

So here's what that would look like:

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()]

We are able to maintain the invariant that the metatype used for the key describes the element type of the array value through the implementation of loadedEntities, as we only store a [T] value for a T.self key.


There is a potential performance issue here however from using a getter and setter; the array values will suffer from copying on mutation (mutating calls the getter to get a temporary array, that array is mutated and then the setter is called).

(hopefully we get generalised addressors soon...)

Depending on whether this is a performance concern, you could implement a static method to perform in-place mutation of the array values:

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
}

There's quite a bit going on here, let's unpack it a bit:

  • We first remove the array from the dictionary (if it exists).
  • We then apply the mutations to the array. As it's now uniquely referenced (no longer present in the dictionary), it can be mutated in-place.
  • We then put the mutated array back in the dictionary (using defer so we can neatly return from body and then put the array back).

We're using with(_:_:) here in order to ensure we have write access to _loadedEntities throughout the entirety of withLoadedEntities(_:) to ensure that Swift catches exclusive access violations like this:

EntityCollection<Foo>.withLoadedEntities { entities in
  entities += [Foo(), Foo()]
  EntityCollection<Foo>.withLoadedEntities { print($0) } // crash!
}

这篇关于快速解决泛型类型的静态存储属性的最佳选择是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆