Вызов инициализаторов протокола Swift - PullRequest
0 голосов
/ 01 февраля 2020

Swift 5, на XCode 11.2.1. Я запрограммировал в Java навсегда, и на данный момент я обладаю лишь умеренными навыками в Swift, поэтому некоторые мои знания окрашены Java идиомами.

Предположим, мы предоставляем протокол или, возможно, псевдо - абстрактный класс, который можно реализовать в отдельном модуле. Затем программист передает ссылку на свой класс обратно к коду в первом модуле, который создает экземпляр своего класса. Теперь в Java это было трудно, потому что вы не могли дать никаких гарантий, какие методы инициализаторов / stati c определит подкласс. Однако в Swift вы можете. Это аккуратно. За исключением того, что я еще не нашел способ их использования. Что бы я ожидал , так это то, что у вас мог бы быть такой код, например

protocol FooP {
  init(s: String)
}

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

... OOOOORRRR, который на самом деле работает, именно так, как я надеялся. Я думаю, что в моих тестах я просто не смог найти правильную комбинацию CLASS / .Type / .self, чтобы подсказать мне возможность успеха.

Поскольку я не нашел никакой другой информации об этой возможности, несмотря на поиск в течение 30-60 минут, я отвечу на свой вопрос.

1 Ответ

0 голосов
/ 01 февраля 2020

Как оказалось, вы МОЖЕТЕ использовать методы stati c, заданные протоколом, даже когда задействованные классы (не их экземпляры) приводятся к их протоколу. Вы можете даже использовать обобщенные элементы, чтобы сделать примерный метод instantiate более удобным, чтобы иметь возвращаемый тип того типа, который вы ему дали, хотя это вызывает другую проблему, как показано ниже. Вот код, демонстрирующий то, что вы можете и не можете сделать:

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 в l oop. Это похоже на разумную строку кода. Дайте мне знать, если у вас есть объяснение. Возможно, относительно <T : ProtocolTest>, ProtocolTest технически не соответствует ProtocolTest, или, возможно, это строгий подкласс? Точно сказать не могу. Было бы неплохо иметь возможность использовать один и тот же метод для обоих целей, но я довольно доволен тем, что уже могу сделать. Этого открытия может быть достаточно, чтобы заставить меня полюбить Свифта, хаха.

...