我的应用在启动时加载了一个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之类的东西中知道要实例化哪些类型?
答案 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种不同的方式。 (如果以后发生这种情况,我会在有示例的情况下对通用进行重构。)在得到几个具体示例之前,您对该问题的了解不够充分,无论如何,您的通用解决方案可能都是错误的。
好的,我们假设这是一个非常通用的JSON格式,你必须解析它不知道它将会是什么(可能就像你提到的NIB格式) 。第一个也是最明显的答案:使用您现有的解决方案。您担心的是,向后查找它们很困难(类型 - >标识符),但编写方法以使其变得简单是微不足道的。除非你有数千甚至数百万种类型,否则时间上的差异是微不足道的;对于小型集合,线性搜索甚至可以比字典更快。
接下来就是为每个协议写一个typeMap
(即明确地为BasicType
而不是使它成为通用的。在stdlib中有几种情况他们这样做。有时你只是必须这样做。
当然,您可以创建一个包含信息的TypeDefinition
结构(如类型橡皮擦)。在实践中,这实际上只是解决了问题。如果有一堆协议,最终你需要复制一些代码。
但几乎总是当我走这条路的时候,我发现它过于设计,直接编写愚蠢的代码就是简单易用的解决方案。
当然,请仔细查看Swift 4中的新JSONDecoder
。如果可以的话,您希望朝着这个方向前进。