调用 Swift 协议初始化器

Invoking Swift Protocol Initializers

Swift 5,在 XCode 11.2.1。我一直在 Java 中编程,目前在 Swift 方面只是中等水平,所以我的一些理解被 Java 习语所影响。

假设我们提供一个协议,或者可能是一个伪抽象class,它可以在一个单独的模块中实现。然后程序员将对 class 的引用传递回第一个模块中的代码,该模块实例化 class。现在,在 Java 中这很困难,因为您无法保证 subclass 将定义什么 initializers/static 方法。但是,在 Swift 中,您可以。这很整洁。除了,我还没有找到使用它们的方法。我 期望 的是您可以使用

这样的代码
protocol FooP {
  init(s: String)
}

func instantiate(clazz: FooP.Type) -> FooP {
  return clazz.init(s: "qwerty")
}

...OOOOORRRR 确实有效,正如我所希望的那样。我想在我的测试中我只是没能找到 CLASS/.Type/.self 的正确组合来提示我成功的可能性。

由于我还没有找到关于这种可能性的任何其他信息,尽管搜索了 30-60 分钟,我还是会回答我自己的问题。

事实证明,您可以使用协议提供的静态方法,即使涉及的 类(不是其实例化)被强制转换为它们的协议。您甚至可以使用泛型使示例 instantiate 方法更方便地具有您提供的类型的 return 类型,尽管这会导致不同的问题,如下所示。这里有一堆代码演示了你能做什么和不能做什么:

public protocol ProtocolTest {
    init(s: String)

    static func factory(s: String) -> Self
}

public class Foo : ProtocolTest, CustomStringConvertible {
    public let s: String

    public required init(s: String) {
        self.s = s
    }

    public class func factory(s: String) -> Self {
        return Self(s: s)
    }

    public var description: String { return "{\(Self.self)|s:\"\(s)\"}" }
}

public class Bar : Foo {
}

public func instantiateGeneric<T : ProtocolTest>(clazz: T.Type) -> T {
    return clazz.init(s: "qwertyGeneric")
}

public func instantiateNongeneric(clazz: ProtocolTest.Type) -> ProtocolTest {
    return clazz.init(s: "qwertyNongeneric")
}

public func run() {
    let ptts: [ProtocolTest.Type] = [Foo.self, Bar.self]

    // Success
    let aInit : Bar = Bar.init(s: "qwertyInit")
    // Success
    let aGeneric : Bar = instantiateGeneric(clazz: Bar.self)
    // Compile error: Cannot convert value of type 'ProtocolTest' to specified type 'Bar'
    let aNongeneric : Bar = instantiateNongeneric(clazz: Bar.self)

    for ptt in ptts {
        // Success
        let bInit : ProtocolTest = ptt.init(s: "qwertyInit")
        // Compile error: Protocol type 'ProtocolTest' cannot conform to 'ProtocolTest' because only concrete types can conform to protocols
        let bGeneric1 : ProtocolTest = instantiateGeneric(clazz: ptt)
        // Compile error: Cannot invoke 'instantiateGeneric' with an argument list of type '(clazz: ProtocolTest.Type)'
        let bGeneric2 = instantiateGeneric(clazz: ptt)
        // Success
        let bNongeneric : ProtocolTest = instantiateNongeneric(clazz: ptt)
        // This works too, btw:
        let bFactory : ProtocolTest = ptt.factory(s: "qwertyFactory")
    }
}

我不确定循环中的 instantiateGeneric 究竟是怎么回事。这似乎是一行合理的代码。如果您有任何解释,请告诉我。也许关于 <T : ProtocolTest>ProtocolTest 在技术上不符合 ProtocolTest,或者它必须是一个严格的子类?不确定。能够对两种用途使用相同的方法会很好,但我已经对我能做的事情感到非常满意。这个发现可能足以让我真正喜欢Swift,哈哈。