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

时间:2016-06-22 09:04:42

标签: swift generics orm

由于swift中的泛型类型不支持静态存储属性,我想知道什么是一个很好的选择。

我的具体用例是我想在swift中构建一个ORM。我有一个Entity协议,它有一个主键的关联类型,因为一些实体将有一个整数作为id,一些将有一个字符串等。这样就成了Entity协议通用的。

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

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

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

9 个答案:

答案 0 :(得分:14)

Swift当前不支持泛型类型的静态存储属性的原因是通用占位符的每个特化都需要单独的属性存储 - 有关此{{}的更多讨论3}}

然而,我们可以使用全局字典来实现它(请记住,静态属性只不过是命名空间到给定类型的全局属性)。尽管如此,还是有一些障碍需要克服。

第一个障碍是我们需要一种钥匙类型。理想情况下,这将是该类型的通用占位符的元类型值;但是,元类型目前不符合协议,因此不是Hashable。要解决此问题,in this Q&A

/// 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
  }

  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]值1}}密钥。

然而,使用getter和setter存在潜在的性能问题;数组值将受到复制的影响(mutating调用getter来获取一个临时数组,该数组被突变,然后调用setter)。

(希望我们很快得到广义的地址......)

根据这是否是性能问题,您可以实现静态方法来执行数组值的就地变异:

T.self

这里有相当多的事情,让我们稍微解压一下:

  • 我们首先从字典中删除数组(如果存在)。
  • 然后我们将突变应用于阵列。由于它现在被唯一引用(不再出现在字典中),因此可以就地进行变异。
  • 然后我们将变异的数组放回字典中(使用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(_:_:)中拥有对_loadedEntities的写入权限,以确保Swift能够捕获这样的独占访问权限:< / p>

withLoadedEntities(_:)

答案 1 :(得分:9)

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

private extension Array where Element: String {
    static var allIdentifiers: [String] {
        get {
            return ["String 1", "String 2"]
        }
    }
}

思想?

答案 2 :(得分:3)

一小时前,我的问题几乎和你的一样。我还希望有一个BaseService类和许多其他服务从这个继承,只有一个静态实例。问题是所有服务都使用自己的模型(例如:UserService使用UserModel ..)

简而言之,我尝试了以下代码。它有效!。

class BaseService<Model> where Model:BaseModel {
    var models:[Model]?;
}

class UserService : BaseService<User> {
    static let shared = UserService();

    private init() {}
}

希望它有所帮助。

我认为诀窍是BaseService本身不会直接使用,所以不需要有静态存储属性。 (P.S.我希望swift支持抽象类,BaseService应该是)

答案 3 :(得分:1)

事实证明,虽然不允许使用属性,但方法和计算属性都是。所以你可以这样做:

class MyClass<T> {
    static func myValue() -> String { return "MyValue" }
}

或者:

class MyClass<T> {
    static var myValue: String { return "MyValue" }
}

答案 4 :(得分: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 { ... }

class 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 { ... }

答案 5 :(得分:0)

这并不理想,但这是我提出的满足我需求的解决方案。

我正在使用非泛型类来存储数据。在我的情况下,我用它来存储单身人士。我有以下课程:

private class GenericStatic {
    private static var singletons: [String:Any] = [:]

    static func singleton<GenericInstance, SingletonType>(for generic: GenericInstance, _ newInstance: () -> SingletonType) -> SingletonType {
        let key = "\(String(describing: GenericInstance.self)).\(String(describing: SingletonType.self))"
        if singletons[key] == nil {
            singletons[key] = newInstance()
        }
        return singletons[key] as! SingletonType
    }
}

这基本上只是一个缓存。

函数singleton采用负责单例的泛型和一个返回单例的新实例的闭包。

它从通用实例类名生成一个字符串键,并检查字典(singletons)以查看它是否已存在。如果没有,它会调用闭包来创建和存储它,否则它会返回它。

从泛型类中,您可以使用Caleb所描述的静态属性。例如:

open class Something<G> {
    open static var number: Int {
        return GenericStatic.singleton(for: self) {
            print("Creating singleton for \(String(describing: self))")
            return 5
        }
    }
}

测试以下内容,您可以看到每个单例只为每个泛型类型创建

print(Something<Int>.number) // prints "Creating singleton for Something<Int>" followed by 5
print(Something<Int>.number) // prints 5
print(Something<String>.number) // prints "Creating singleton for Something<String>"

此解决方案可以提供一些深入了解为什么在Swift中不会自动处理这个问题的方法。

我选择通过将单例静态设置为每个通用实例来实现此功能,但这可能是您的意图或需要。

答案 6 :(得分:0)

根据需要支持的类型以及inheritance是否(不是)您的选择,条件一致性也可以解决问题:

final class A<T> {}
final class B {}
final class C {}

extension A where T == B {
    static var stored: [T] = []
}

extension A where T == C {
    static var stored: [T] = []
}

let a1 = A<B>()
A<B>.stored = [B()]
A<B>.stored

let a2 = A<C>()
A<C>.stored = [C()]
A<C>.stored

答案 7 :(得分:0)

好吧,我也遇到了同样的问题,并且能够为此解决逻辑问题。我必须使用通用类作为处理程序来创建urlsession的静态实例。

class ViewController: UIViewController {
override func viewDidLoad() {
    super.viewDidLoad()
    let neworkHandler = NetworkHandler<String>()
    neworkHandler.download()
    neworkHandler.download()
}


class SessionConfigurator: NSObject{
static var configuration:URLSessionConfiguration{
    let sessionConfig = URLSessionConfiguration.background(withIdentifier: "com.bundle.id")
    sessionConfig.isDiscretionary = true
    sessionConfig.allowsCellularAccess = true
    return sessionConfig
}
static var urlSession:URLSession?


class NetworkHandler<T> :NSObject, URLSessionDelegate{
  func download(){
    if SessionConfigurator.urlSession == nil{
    SessionConfigurator.urlSession = URLSession(configuration:SessionConfigurator.configuration, delegate:self, delegateQueue: OperationQueue.main)
    }
}

答案 8 :(得分:-2)

这样的东西?

background.js // to add a runTime.onMessage listener
contentscript.js // to inject custom.js, add a listener to a custom event and fire the runTime.sendMessage
custom.js // to retrieve myVar and dispatch the custom event.
devtools.js // to create the extension Panel

devtools.html // contains just devtools.js
panel.html // basic html structure of my panel, no js.