在Swift中返回instancetype

时间:2015-10-18 16:00:56

标签: swift swift2 swift-extensions

我试图进行此扩展:

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
    }
}

但我收到编译错误:

  

错误:无法转换类型' UIViewController'的返回表达式至   返回类型'自我'

有可能吗?我也希望将其设为init(storyboardName: String, storyboardId: String)

4 个答案:

答案 0 :(得分:65)

Using 'self' in class extension functions in 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)
    }
}

答案 1 :(得分:15)

Self在编译时确定,而不是运行时。在您的代码中,UIViewController完全等同于UIViewController,而不是&#34;正好调用此类的子类。&#34;这将返回as,调用者必须UIViewController进入正确的子类。我认为这是你想要避免的(尽管它是#34;正常的Cocoa&#34;这样做的方式,所以只返回initialize可能是最好的解决方案。)

注意:在任何情况下都不应将函数命名为NSObject。这是as的现有类函数,最多会造成混淆,最坏的情况是错误。

但是如果你想避免调用者的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 } ,子类化通常不是在Swift中添加功能的工具。相反,您通常需要泛型和协议。在这种情况下,只需要泛型。

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

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

numberOfSectionsInTableView

答案 2 :(得分:0)

另一种方法是使用协议,该协议还允许您返回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
    }
}

答案 3 :(得分:0)

更清洁的解决方案(至少在视觉上更整洁):

class func initialize(storyboardName: String, storyboardId: String) -> Self {
    // The absurdity that's Swift's 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()
}