什么是swift中泛型类型的静态存储属性的一个很好的替代方案?

Eve*_*ert 24 generics orm swift

由于swift中的泛型类型(静态)不支持静态存储属性,我想知道什么是好的替代方案.

我的具体用例是我想在swift中构建一个ORM.我有一个Entity协议,它有一个主键的关联类型,因为一些实体将有一个整数作为他们id,一些将有一个字符串等.所以这使Entity协议通用.

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

我想在它上面使用静态属性EntityCollection来存储已经从数据库中提取的所有实体.因此,如果EntityCollection要从数据库中获取同一实体的两个单独实例,则只会查询一次数据库.

你们有什么想法我能做到这一点吗?

Ham*_*ish 17

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 }
}
Run Code Online (Sandbox Code Playgroud)

第二个是字典的每个值可以是不同的类型; 幸运的是,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()]
Run Code Online (Sandbox Code Playgroud)

我们能够通过执行来维护用于密钥的元类型描述数组值的元素类型的不变量loadedEntities,因为我们只存储密钥的[T]T.self.


但是,使用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
}
Run Code Online (Sandbox Code Playgroud)

这里有很多内容,让我们解压一下:

  • 我们首先从字典中删除数组(如果存在).
  • 然后我们将突变应用于阵列.因为它现在被唯一引用(不再存在于字典中),所以它可以就地变异.
  • 然后我们将变异的数组放回字典中(使用,defer这样我们可以整齐地返回body然后将数组放回).

我们在with(_:_:)这里使用是为了确保我们在_loadedEntities整个过程中都有写访问权限,withLoadedEntities(_:)以确保Swift能够捕获这样的独占访问冲突:

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


Jer*_*rry 9

我不确定我是否喜欢这个,但我使用了静态计算属性:

private extension Array where Element: String {
    static var allIdentifiers: [String] {
        get {
            return ["String 1", "String 2"]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

思考?

  • 我认为这会编译但每次创建一个新副本并且表现得像非静态属性 (6认同)

Dav*_*rry 0

我能想到的就是分离出源的概念(集合来自哪里),然后分离集合本身。然后让源负责缓存。此时,源实际上可以是一个实例,因此它可以保留它想要/需要的任何缓存,并且您的 EntityCollection 仅负责维护源周围的 CollectionType 和/或 SequenceType 协议。

就像是:

protocol Entity {
    associatedtype IdType : Comparable
    var id : IdType { get }
}

protocol Source {
    associatedtype EntityType : Entity

    func first() -> [EntityType]?
    func next(_: EntityType) -> [EntityType]?
}

class WebEntityGenerator <EntityType:Entity, SourceType:Source where EntityType == SourceType.EntityType> : GeneratorType { ... }
Run Code Online (Sandbox Code Playgroud)

类 WebEntityCollection : SequenceType { ... }

如果您有典型的分页 Web 数据界面,则可以使用。然后你可以按照以下方式做一些事情:

class WebQuerySource<EntityType:Entity> : Source {
    var cache : [EntityType]

    ...

    func query(query:String) -> WebEntityCollection {
        ...
    }
}

let source = WebQuerySource<MyEntityType>(some base url)

for result in source.query(some query argument) {
}

source.query(some query argument)
      .map { ... } 
      .filter { ... }
Run Code Online (Sandbox Code Playgroud)