引用协议元类型时关联类型的问题

Problem with associtedtype when referring to a protocol metatype

这是我的问题:

假设我有一个协议 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 指的是一个协议,我得到 "Type 'BMeta' does not conform to protocol '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 类型系统。

也就是说,这是一种非常不灵活的方法。它要求每个类型都接受一个普通的 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)