Reputation: 1048
I want to hold onto a Interactor class in a BasePresenter class, but for the life of me I can't figure out how on earth to use generics easily in Swift. I want to have a function in the base class (like BasePresenter interactorForType).
public class InboxListPresenter: BasePresenter, ObservableObject {
public init(interactor: InboxListInteractor) {
super.init(router: InboxListRouter(viewData: viewData), interactor: interactor)
}
func findInboxNotifications() {
interactorForType(type: InboxListInteractor.self).findInboxNotifications() { inboxNotifications, errorCode in
// do something
}
}
}
Here's the BasePresenter where I want to hold the generic object. I'm trying to use protocols (ILibertyInteractor)
open class BasePresenter {
public let router:ILibertyRouter
public let interactor:ILibertyInteractor
public init(router:ILibertyRouter, interactor:ILibertyInteractor) {
self.router = router
self.interactor = interactor
}
func interactorForType<T>(type: T.Type) -> T {
return interactor as! T
}
func routerForType<T>(type: T.Type) -> T {
return router as! T
}
}
The above interactorForType does a force unwrap on interaction that I want to avoid. Also, I don't really want to pass in the object either. I'd like to use protocols and generics to have a generic function that returns type T.
Below is the start of what I was trying to allow returning the type back within the presenter above.
public protocol ILibertyPresenter {
var router:ILibertyRouter { get }
}
public protocol ILibertyRouter {
}
public protocol ILibertyInteractor {
// associatedtype T
// func trueInteractor() -> T
}
Upvotes: 2
Views: 1699
Reputation: 2785
There are two things here. There is the question you ask, "how to store a generic?", and then there is what your example code seems to be trying to do.
The short answer for how to store a generic (assuming going through Any
is undesirable - and it is undesirable), is to store it in a generic.
If want to hold on to a generic type as its explicit type, the thing holding onto it also have to be generic and specialized on the same type. That would require your InboxListPresenter
to be generic too. So you could do this:
open class BasePresenter <InteractorType: IteractorProtocol>{
public let router:ILibertyRouter
public let interactor: InteractorType // You can do this now
public init(router:ILibertyRouter, interactor:ILibertyInteractor) {
self.router = router
self.interactor = interactor
}
/* you don't need this now, because InteractorType is part of the generic class definition
func interactorForType<T>(type: T.Type) -> T {
return interactor as! T
}
*/
func routerForType<T>(type: T.Type) -> T {
return router as! T
}
}
In this example, I specified that IteractorType
conforms to InteractorProtocol
.
Under some circumstances you eliminate the need for the surrounding type to be generic, allowing you to do this:
open class BasePresenter{
public let router:ILibertyRouter
public let interactor: InteractorProtocol
public init(router:ILibertyRouter, interactor:InteractorProtocol) {
self.router = router
self.interactor = interactor
}
/* you don't need this now, because InteractorProtocol should define the interface for all Interactors
func interactorForType<T>(type: T.Type) -> T {
return interactor as! T
}
*/
func routerForType<T>(type: T.Type) -> T {
return router as! T
}
}
Specifically you can do that if InteractorProtocol
doesn't have, as the dreaded Swift compiler error will say, "Self or associated type constraints". Basically if you have where
clauses or other protocol conformance constraints on the protocol or on the associated types in the protocol, then you can only use the protocol to constrain generic parameters.
If you it doesn't have Self
or associated type constraints, then you can use it as though it were an ordinary type, including as the type for a stored property.
The code you've shown is basically a poster child for classic OOP-style inheritance. That's not the only way to solve the problem, but looking at it, it screams for an Interactor
base class that defines the interface, and for concrete implementations to do the actual work. I don't know what Interactor
has to do, but if it's sufficiently complex, you can apply all sorts of design patterns to avoid having to have a gazillion subclasses of it. And you can borrow the idea from Cocoa/CocoaTouch of declaring delegate protocols so you can modify the behavior that way.
Anyway the inheritance approach is fairly straight forward.
open class Iteractor {
// Just replace the closure parameter types with whatever they're supposed to be
func findInboxNotifications((Any, ErrorCode) -> Void) { fatalError("Implement in subclass!") }
}
Then you use that as your base class for your concrete Interactor types.
final class AConcreteInteractor: Iteractor {
override func findInboxNotifications ((Any, ErrorCode) -> Void) {
/* Do actual work here */
}
}
Then in your BasePresenter
:
open class BasePresenter {
public let router:ILibertyRouter
public let interactor: Iteractor
public init(router:ILibertyRouter, interactor: Iteractor) {
self.router = router
self.interactor = interactor
}
/* This isn't needed because you'll use `interactor` polymorphically`
func interactorForType<T>(type: T.Type) -> T {
return interactor as! T
}
*/
...
}
InboxListPresenter
would then look like this
public class InboxListPresenter: BasePresenter, ObservableObject {
public init(interactor: InboxListInteractor) {
super.init(router: InboxListRouter(viewData: viewData), interactor: interactor)
}
func findInboxNotifications() {
iteractor.findInboxNotifications() { inboxNotifications, errorCode in
// do something
}
}
}
I don't think protocols is a good fit in this case, but in some ways, protocols can substitute for class-based inheritance; but since you have to store the interactor, that puts you right back at having to make sure you don't have Self
or associated type constraints.
When used this way the only advantage to protocols over inheritance is that you get some better compiler checks - for example, it can ensure that all required implementation methods are actually implemented by the types conforming to the protocol, whereas in class inheritance, the base class always defines them (because unlike Java, Swift doesn't let you define a purely abstract base class, nor does it allow you specify "pure" virtual methods likes C++). You pay for that benefit in being more constrained how you define them so that you can use them as stored property types.
I would say that the code you present in your question wants to use class-based inheritance for your Interactors. Unless you have some good reason not to, I would just go with that rather than try to force the square peg into a round hole.
Upvotes: 2