如何知道从JSON之类的东西中实例化哪种类型?

时间:2017-06-12 14:17:43

标签: swift generics

我的应用在启动时加载了一个JSON文件。 JSON文件包含创建不同类型的各种实例的说明。作为一个基本的例子,我有一个名为BasicType的协议,其中包含一些扩展,以使一些内置的Swift类型符合它:

protocol BasicType {

}

extension Int: BasicType { }

extension Float: BasicType { }

extension String: BasicType { }

我还有一本字典,用于映射这些类型的姓名' (在JSON中找到)类型本身:

let basicTypes = [
    "integer": Int.self,
    "float": Float.self,
    "string": String.self
] as [String : BasicType.Type]

当我从JSON加载BasicType时,我使用上面的字典查找JSON指定的名称,以了解要实例化的类型(实际上,协议还定义了初始化程序,所以这个有可能)。除了BasicType之外,我还有一些其他协议以完全相同的方式工作,每个协议都有自己的字典映射。

例如,我的JSON可能包含我要实例化的BasicType数组:

{
    "basic_types": [{
        "type": "integer",
        ...
    }, {
        "type": "integer",
        ...
    }, {
        "type": "string",
        ...
    }, {
        "type": "float",
        ...
    }]
}

省略号表示传递给初始化器的其他属性,例如整数应该是什么值。实际上,这些是具有各种属性的自定义结构和类。在我的Swift代码中,我打开basic_types数组,查看每个JSON对象的type键,查找相应的类型并初始化它们。所以我加载的数组包含两个Int,一个String和一个Float

这类似于UIKit从故事板中实例化视图的方式。视图的类名存储在故事板中,在这种情况下,它可能使用NSClassFromString之类的东西来执行映射。我不想依赖Objective-C运行时。

这种方法的问题在于,如果我已经拥有类型或实例,则很难查找类型的名称,因为我必须低效地迭代字典才能进行搜索。

相反,我认为更好的解决方案可能是将每个类型的名称(或类型标识符')静态地包含在类型本身的变量中,然后从中生成类型映射。所以,我创建了一个新协议:

protocol TypeIdentifiable {
    static var typeIdentifier: String { get }
}

并使BasicType(以及所有其他类似协议)继承自它:

protocol BasicType: TypeIdentifiable {

}

这意味着我需要直接在类型中提供名称:

extension Int: BasicType {
    static let typeIdentifier = "integer"
}

extension Float: BasicType {
    static let typeIdentifier = "float"
}

extension String: BasicType {
    static let typeIdentifier = "string"
}

我创建了一个函数(编译),我打算通常采用符合TypeIdentifiable的协议并包含一个类型数组,并生成一个包含映射的字典:

func typeMap<T : TypeIdentifiable>(_ types: [T.Type]) -> [String : T.Type] {
    return Dictionary(uniqueKeysWithValues: types.map { type in
        (key: type.typeIdentifier, value: type)
    })
}

然后我可以用以下代码替换字典文字:

let basicTypes = typeMap([
    Int.self,
    Float.self,
    String.self
] as [BasicType.Type])

这样,如果我有一个类型,我可以通过访问其静态属性轻松获取其名称,如果我有名称,我可以通过生成的字典获取类型。

不幸的是,这不起作用:

  

错误:无法转换类型&#39; [BasicType.Type]&#39;的值预期参数类型&#39; [_。类型]&#39;

我认为在Swift中禁止使用协议作为通用类型,这可能使我无法做我想做的事情。

我的问题是: 有没有办法做我想做的事情?否则,在更一般的层面上,是否有更好的方法可以从JSON之类的东西中知道要实例化哪些类型?

1 个答案:

答案 0 :(得分:1)

这是SR-3038,大致&#34;按设计。&#34;协议不符合自身,因此无法满足通用要求。 BasicType不能T,因为BasicType是协议。为什么?好吧,想象一下,我已经编写了这段代码(我希望它与您想要的内容非常相似):

protocol P {
    init()
}

func makeOne<T:P>(what: T.Type) -> T {
    return T.init()
}

真棒。这是合法的。现在,如果协议符合自己的要求,我可以编写这段代码:

makeOne(what: P.self)

那呼叫init是什么? Swift通过仅允许具体类型符合协议来修复此僵局。由于BasicType不符合BasicType,因此它也不符合TypeIdentifiable,也不能符合T

那我们该怎么做?

  • 首先也是最重要的 - 最重要的:你真的需要这个是通用的吗?您实际计划将它用于完全不同的JSON格式多少次?你能直接编写解析代码(理想情况下是新的Codeable)吗?试图编写复杂的JSON解析器来解析简单的JSON格式的漏洞可能会泛滥WWDC。我的经验法则是避免使事物变得通用,除非我已经知道它将被使用至少3种不同的方式。 (如果以后发生这种情况,我会在有示例的情况下对通用进行重构。)在得到几个具体示例之前,您对该问题的了解不够充分,无论如何,您的通用解决方案可能都是错误的。

    < / LI>
  • 好的,我们假设这是一个非常通用的JSON格式,你必须解析它不知道它将会是什么(可能就像你提到的NIB格式) 。第一个也是最明显的答案:使用您现有的解决方案。您担心的是,向后查找它们很困难(类型 - >标识符),但编写方法以使其变得简单是微不足道的。除非你有数千甚至数百万种类型,否则时间上的差异是微不足道的;对于小型集合,线性搜索甚至可以比字典更快。

  • 接下来就是为每个协议写一个typeMap(即明确地为BasicType而不是使它成为通用的。在stdlib中有几种情况他们这样做。有时你只是必须这样做。

  • 当然,您可以创建一个包含信息的TypeDefinition结构(如类型橡皮擦)。在实践中,这实际上只是解决了问题。如果有一堆协议,最终你需要复制一些代码。

但几乎总是当我走这条路的时候,我发现它过于设计,直接编写愚蠢的代码就是简单易用的解决方案。

当然,请仔细查看Swift 4中的新JSONDecoder。如果可以的话,您希望朝着这个方向前进。