在Swift中返回instancetype

Chi*_*buZ 35 swift swift-extensions swift2

我正在尝试进行此扩展:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

但我得到编译错误:

错误:无法将'UIViewController'类型的返回表达式转换为返回类型'Self'

可能吗?我也想成为init(storyboardName: String, storyboardId: String)

Mar*_*n R 66

类似于在Swift中的类扩展函数使用'self',您可以定义一个通用的辅助方法,它从调用上下文中推断出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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后

let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")
Run Code Online (Sandbox Code Playgroud)

编译,类型推断为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
    }
}
Run Code Online (Sandbox Code Playgroud)

另一种可能的解决方案,使用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)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 嗨,马丁,您能否解释一下编译器如何识别泛型类型T并能够将其转换为Self? (9认同)

Rob*_*ier 15

Self是在编译时确定的,而不是运行时.在你的代码中,Self完全等同于UIViewController,而不是"恰好调用它的子类".这将返回UIViewController,调用者必须将as它放入正确的子类中.我认为这是你试图避免的(虽然这是"正常的可可"方式,所以只返回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
}
Run Code Online (Sandbox Code Playgroud)

这不是一种类方法.这只是一个功能.这里没有必要上课.

let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的答案.我有一个非常类似的问题.我想要一个协议,可以实现从故事板中实例化的类,这样调用者就不必知道实例化的视图控制器的确切类型.我放弃了这种方法.也许我会再试一次,但在协议中使用泛型. (2认同)