Return instancetype in Swift

Clash Royale CLAN TAG#URR8PPP
Return instancetype in Swift
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)
init(storyboardName: String, storyboardId: String)
3 Answers
3
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.
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:
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)
Hi Martin, Could you please explain how the compiler be able to identify the generic type T and be able to cast it to Self?
– Adithya
Sep 10 '16 at 6:13
people who say "private class func" rather than "static func" have a lot of style. :)
– Fattie
Feb 5 '17 at 15:21
it's interesting that if you just have it return the base type - don't bother with a generic - it works perfectly at runtime (returning the actual subclass) but you'd have to cast results in code, at "editor time". example: stackoverflow.com/a/42053648/294884
– Fattie
Feb 5 '17 at 15:34
@JoeBlow: Yes, the goal here was just to avoid the cast. – "static" is "class + final" and therefore unrelated to "private".
– Martin R
Feb 5 '17 at 16:29
quite. thanks @MartinR. It took me days to figure the answer to this conceptually similar issue stackoverflow.com/q/42041150/294884
– Fattie
Feb 5 '17 at 19:48
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).
Self
Self
UIViewController
UIViewController
as
UIViewController
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.
initialize
NSObject
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.
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
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)
This is a great answer. I had a very similar problem a while back. I wanted a protocol that classes that could be instantiated from a storyboard could implement, so that the caller didn't have to know the exact type of view controller being instantiated. I gave up on that approach. Maybe I'll try it again, but with a generic in the protocol.
– NRitH
Oct 18 '15 at 16:30
Another way is to use a protocol, which also allows you to return Self.
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
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Please don't post answers inside your question.
– Rizier123
Nov 7 '16 at 16:29