问题
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