Return instancetype in Swift

孤街浪徒 提交于 2019-11-26 01:30:03

Similar as in Using 'self' in class extension functions in Swift, you can define a generic helper method which infers the type of self from the calling context:

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

Then

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

compiles, and the type is inferred as MyViewController.


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

Another possible solution, using 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)
    }
}

Self is determined at compile-time, not runtime. In your code, Self is exactly equivalent to UIViewController, not "the subclass that happens to be calling this." This is going to return UIViewController and the caller will have to as it into the right subclass. I assume that's what you were trying to avoid (though it is the "normal Cocoa" way to do it, so just returning UIViewController is probably the best solution).

Note: You should not name the function initialize in any case. That's an existing class function of NSObject and would cause confusion at best, bugs at worst.

But if you want to avoid the caller's as, subclassing is not usually the tool to add functionality in Swift. Instead, you usually want generics and protocols. In this case, generics are all you need.

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
}

This isn't a class method. It's just a function. There's no need for a class here.

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

Another way is to use a protocol, which also allows you to return 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
    }
}

A cleaner solution (at least visually tidier):

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()
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!