Return instancetype in Swift

老子叫甜甜 提交于 2019-11-26 01:07:46

问题


I\'m trying to make this extension:

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

But I get compile error:

error: cannot convert return expression of type \'UIViewController\' to return type \'Self\'

Is it possible? Also I want to make it as init(storyboardName: String, storyboardId: String)


回答1:


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



回答2:


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)



回答3:


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



回答4:


A cleaner solution (at least visually tidier):

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
}

Older solution

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


来源:https://stackoverflow.com/questions/33200035/return-instancetype-in-swift

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