swift - 引用协议元类型时关联类型的问题
问题描述
这是我的问题:
假设我有一个associatedtype
引用其元类型的协议:
protocol TestMeta {
associatedtype T
var x : T.Type { get }
var y : T { get }
}
如果我创建一个具体类型的结构,没问题:
struct AMeta : TestMeta {
var x : Int.Type
var y : Int
}
但是如果associatedtype
引用协议,我会得到“类型'BMeta'不符合协议'TestMeta'”错误:
protocol P { }
struct BMeta : TestMeta {
var x : P.Type
var y : P
}
(即使我添加typealias
定义来帮助推理引擎)
当然,如果我不引用元类型,那么一切都适用于具体的类型和协议,即使我有其他未在协议中定义的元类型变量:
protocol TestNoMeta {
associatedtype T
var z : T { get }
}
struct ANoMeta : TestNoMeta {
var z : Int
var t : Int.Type
}
struct BNoMeta : TestNoMeta {
var z : P
var t : P.Type
}
那么,如果有人能解释我做错了什么?我怎样才能实现我的目标?提前致谢。
编辑:但正如@New Dev 指出的那样,我没有解释我在寻找什么。我希望能够做这样的事情:
struct S : P { }
let b = BMeta(x: S.self, y: S())
知道仍然使用 NoMeta 协议进行编译:
let bb = BNoMeta(z: S(), t: S.Type)
编辑2:最后我希望做这样的事情:
protocol P {
init()
}
extension TestMeta {
func build() -> P {
return x.init()
}
}
struct BMeta : TestMeta {
var x : P.Type
var y : P
}
struct S : P { }
let b = BMeta(x: S.self, y: S())
let c = b.build()
编辑3:好的,好的,这是我的真实用例,我认为简化事情会更好,但似乎不是......
protocol Initializable {
init()
}
protocol OptionListFactory {
associatedtype Option : Initializable
static var availableOptions: [Option.Type] { get }
}
extension OptionListFactory {
static func build(code: Int) -> Option? {
if code >= 0 && code < availableOptions.count {
return availableOptions[code].init()
}
else {
return nil
}
}
}
protocol Contract : Initializable {
...
}
struct Contract01 : Contract { ... }
struct Contract02 : Contract { ... }
...
struct Contract40 : Contract { ... }
struct ContractFactory : OptionListFactory {
static let availableOptions: [Contract.Type] = [
Contract01.self,
Contract02.self,
...
Contract40.self,
]
}
protocol Element : Initializable {
...
}
struct Element01 : Element { ... }
struct Element02 : Element { ... }
...
struct Element20 : Element { ... }
struct ElementFactory : OptionListFactory {
static let availableOptions: [Element.Type] = [
Element01.self,
Element02.self,
...
Element20.self,
]
}
希望你能更好地理解我的目的......
解决方案
这是标准的“协议不符合协议”问题。Contract 是一个协议,它要求符合类型也符合 Initializable。它本身不是可初始化的。事实上,这是一个典型的例子,说明为什么协议在最一般的情况下不能符合自己。如果是这样,我可以写Contract.init()
并取回……什么?没有办法为“抽象合约”分配内存。
由于Contract不符合Initializable,所以不能直接是Option。今天的 Swift 也缺乏任何机制来讨论协议继承。您不能说 Option 必须是需要 Initializable 的协议。它只是超出了 Swift 类型系统。
也就是说,这是一种相当不灵活的方法。它要求每种类型都接受一个 trivial init
,这对可以符合的类型非常有限。IMO 有更好的方法来实施这种工厂。
摆脱 Initializable,摆脱static
需求(稍后您会看到原因),并用构建器函数替换类型。
protocol OptionListFactory {
associatedtype Option
var availableOptions: [() -> Option] { get }
}
这会导致以这种方式调整扩展:
extension OptionListFactory {
// Remove `static`
func build(code: Int) -> Option? {
if code >= 0 && code < availableOptions.count {
return availableOptions[code]() // Call the builder function
}
else {
return nil
}
}
}
还有一个简单的工厂:
protocol Contract {}
struct Contract01 : Contract {}
struct Contract02 : Contract {}
struct ContractFactory : OptionListFactory {
let availableOptions: [() -> Contract] = [
Contract01.init, // init rather than self
Contract02.init,
]
}
但是,如果您想出售某种类型,需要一些额外的信息来构建呢?这样做很简单。
// Elements need a name
protocol Element {
var name: String { get }
}
struct Element01 : Element { let name: String }
struct Element02 : Element { let name: String }
struct Element20 : Element { let name: String }
struct ElementFactory : OptionListFactory {
let availableOptions: [() -> Element]
// And now we can assign that name
init(prefix: String) {
availableOptions = [
{ Element01(name: prefix + "01") },
{ Element02(name: prefix + "02") },
{ Element20(name: prefix + "20") },
]
}
}
let ef = ElementFactory(prefix: "local")
let e20 = ef.build(code: 2)
// e20?.name == "local20"
通过制作工厂实例而不是使用static
,它们更加灵活并且可以配置。通过使用函数,您可以以您可能没有考虑过的方式进行创作。例如,您可以在每次创建选项时添加调试打印语句:
struct DebugFactory<Base: OptionListFactory>: OptionListFactory {
let availableOptions: [() -> Base.Option]
init(base: Base) {
availableOptions = base.availableOptions.map { f in
return {
print("Creating object")
return f()
}
}
}
}
// Works exactly the same as `ef`, just prints.
let debugFactory = DebugFactory(base: ef)
debugFactory.build(code: 2)
推荐阅读
- android - 如何以编程方式使 ImageView 的宽度 match_parent(不在 XML 中)
- python - 在 Pandas 中将 1-1 转换为 Jan 1 的任何简单方法?
- entity-framework - 从 MS-SQL 读取 Serilog 日志(EntityFramework?)
- server - IBM MQ 服务器已关闭,因为磁盘空间为 100%。如何清除这个队列?
- client - 如何在 Aqueduct auth 中列出/删除客户?
- html - Html 没有更新 ajax 请求(woodpress、woocomerce)
- java - 使用 Pair 作为 LinkedHashMap (Java) 中的键
- python - 小数中的小整数错误,两个参数都应该是 Rational 实例
- list - 有没有办法在循环之外初始化一个列表,它是否被删除并在飞镖中重用?
- linux - 如何解决错误“do_package: ... not shipping in any package”?