Подтвердить что ты не робот

Вернуть тип instancetype в Swift

Я пытаюсь сделать это расширение:

extension UIViewController
{
    class func initialize(storyboardName: String, storyboardId: String) -> Self
    {
        let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
        let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! Self

        return controller
    }
}

Но я получаю ошибку компиляции:

error: невозможно преобразовать возвращаемое выражение типа 'UIViewController' в return type 'Self'

Возможно ли это? Также я хочу сделать это как init(storyboardName: String, storyboardId: String)

4b9b3361

Ответ 1

Как и в случае использования "self" в функциях расширения класса в Swift, вы можете определить универсальный вспомогательный метод, который выводит тип self из вызывающего контекста:

extension UIViewController
{
    class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
    {
        return instantiateFromStoryboardHelper(storyboardName, storyboardId: storyboardId)
    }

    private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
    {
        let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
        let controller = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! T
        return controller
    }
}

затем

let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")

компилируется, и тип выводится как MyViewController.


Обновление для Swift 3:

extension UIViewController
{
    class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
    {
        return instantiateFromStoryboardHelper(storyboardName: storyboardName, storyboardId: storyboardId)
    }

    private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
    {
        let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: storyboardId) as! T
        return controller
    }
}

Другое возможное решение, использующее unsafeDowncast:

extension UIViewController
{
    class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
    {
        let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: storyboardId)
        return unsafeDowncast(controller, to: self)
    }
}

Ответ 2

Self определяется во время компиляции, а не во время выполнения. В вашем коде Self в точности эквивалентен UIViewController, а не "подклассу, который вызывает это". Это вернет UIViewController, и вызывающий должен будет as в правый подкласс. Я предполагаю, что вы пытались избежать (хотя это обычный способ Cocoa ", так что просто возвращение UIViewController, вероятно, лучшее решение).

Примечание. Вы не должны называть функцию initialize в любом случае. Это существующая функция класса NSObject и в лучшем случае вызовет путаницу, ошибки в худшем случае.

Но если вы хотите избежать вызова as, подкласс обычно не является инструментом для добавления функций в Swift. Вместо этого вам обычно нужны дженерики и протоколы. В этом случае вам нужны только дженерики.

func instantiateViewController<VC: UIViewController>(storyboardName: String, storyboardId: String) -> VC {
    let storyboad = UIStoryboard(name name: storyboardName, bundle: nil)
    let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! VC

    return controller
}

Это не метод класса. Это просто функция. Здесь нет необходимости в классе.

let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)

Ответ 3

Другой способ - использовать протокол, который также позволяет вам возвращать Self.

protocol StoryboardGeneratable {

}

extension UIViewController: StoryboardGeneratable {

}

extension StoryboardGeneratable where Self: UIViewController
{
    static func initialize(storyboardName: String, storyboardId: String) -> Self
    {
        let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
        let controller = storyboad.instantiateViewController(withIdentifier: storyboardId) as! Self
        return controller
    }
}

Ответ 4

Более чистое решение (по крайней мере, визуально аккуратнее):

Swift 5.1

class func initialize(storyboardName: String, storyboardId: String) -> Self {
    return UIStoryboard(name: storyboardName, bundle: nil)
        .instantiateViewController(withIdentifier: storyboardId).view as! Self // this is now possible in since 5.1
}

Старое решение

class func initialize(storyboardName: String, storyboardId: String) -> Self {
    // The absurdity that Swift type system. If something is possible to do with two functions, why not let it be just one?
    func loadFromImpl<T>() -> T {
        let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
        return storyboard.instantiateViewController(withIdentifier: storyboardId).view as! T
    }
    return loadFromImpl()
}