Reputation: 77596
Trying to achieve something like the following objective c code in swift
UIViewController<Routable>
but it complains that it can't infer the generic type
I think it wants me to somehow define what subclass of UIViewController to use, but I don't care. I just want a UIViewController that implements Routable
public protocol Routable {
static func provideInstance(id: String?) -> Self?
}
public class Router {
public func getDestinationViewController<T: UIViewController>(url: URL) -> T? where T: Routable {
return nil
}
}
// Generic parameter `T` could not be inferred
let vc = Router().getDestinationViewController(url: URL(string: "www.domain.com/users/1")!)
I know i could use casting to solve the problem and just have the method return either a UIViewController or a Routable, but I rather do it the right way
Upvotes: 0
Views: 247
Reputation: 17724
When you use generic functions the type of the generic parameter is inferred from the type of the variable you assign the result to (or, in the case of a function that returns Void
, from the concrete type of a parameter you pass to an argument of type T
).
In your example, you do not tell the compiler what type your vc
variable is so you get an error. You can fix this easily like so:
class MyViewController: UIViewController, Routable {
public static func provideInstance(id: String?) -> Self? {
return self.init()
}
}
// give `vc` a concrete type so `T` can be inferred:
let vc: MyViewController? = Router().getDestinationViewController(url: URL(string: "www.domain.com/users/1")!)
EDIT:
Since your question seems to be more along the lines of "How do I replicate Objective-C's notion of UIViewController *<Routable>
", which you can't do in Swift, I'll mention that you might find it worthwhile to review your design. When I moved from Objective-C to Swift I thought not being able to use something like UIViewController *<Routable>
would be hard to get around (and even filed a bug report with Apple about it) but in practice it hasn't been an issue.
Your Router
class needs some information in your view controller to be able to route properly. In your example you're looking for a view controller that is associated with a particular URL. The implication is that your UIViewController
has a property that contains this URL, so rather than return a Routable
protocol, the correct approach is to subclass UIViewController like so:
class RoutableViewController: UIViewController {
let url: URL = URL(string: "...")
}
Now your router looks like:
class Router {
var routables = [RoutableViewController]()
func viewControllerWithURL(_ url: URL) -> RoutableViewController? {
return routables.first
}
}
Now you don't need to do any type-casting, and type-safety is (obviously) maintained.
EDIT 2:
Here's a solution that makes every view controller conform to Routable
(I don't think you gain much vs. the solution I proposed in my previous edit, but here it is anyway):
protocol Routable {
var url: URL? { get }
}
// now every UIViewController conforms to Routable
extension UIViewController: Routable {
var url: URL? { return nil }
}
class MyViewController: UIViewController {
private let myURL: URL
override var url: URL? { return myURL }
init(url: URL) {
myURL = url
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class Router {
var viewControllers: [UIViewController] = [
MyViewController(url: URL(string: "foo")!),
UIViewController()
]
func viewController(for url: URL) -> UIViewController? {
for viewController in viewControllers {
if viewController.url == url { return viewController }
}
return nil
}
}
Upvotes: 1
Reputation: 299355
What precise type do you expect vc
to be in this case? (I mean type determinable at compile time, looking at above code, not "some type we don't know until runtime.") If you can't answer that, the compiler can't either.
The right way here is to "return either a UIViewController or a Routable," exactly as you say. That's everything you know at this point. Trying to say something more precise would be incorrect.
The fact that vc
is type-inferred is just a convenience. It still has to have a specific, known type at compile-time. If you can't write it in the source, the compiler can't infer it.
Upvotes: 3