I'm trying to describe a delegate property with a generic protocol without making the class generic:
protocol ActionHandler: class {
associatedtype Action
func handle(_ action: Action)
}
enum SomeAction {
case dismiss
}
class Foo {
typealias Action = SomeAction
// 💣 Protocol 'ActionHandler' can only be used as a generic constraint because it has Self or associated type requirements
weak var handler: ActionHandler?
// Ideally some form of ActionHandler<Action>?
}
class Handler: ActionHandler {
func handle(_ action: SomeAction) {
switch action {
case .dismiss:
print("dismiss")
}
}
}
let handler = Handler()
Foo().handler = handler
I can instead replace the property by a closure, but I don't like this pattern as much, and I would have to do so for every method described in the protocol.
enum SomeAction {
case dismiss
}
class Foo {
typealias Action = SomeAction
var handleAction: ((Action) -> Void)?
}
class Handler {
func handle(_ action: SomeAction) {
switch action {
case .dismiss:
print("dismiss")
}
}
}
let handler = Handler()
Foo().handleAction = handler.handle(_:)
Is there a better solution?
You don't have to make Foo
generic, but you'll need a very good reason not to. Anyway, here's one way that is built on top of your closure approach - What if we wrap those closures in a type?
class AnyActionHandler<Action> : ActionHandler {
var handleClosure: (Action) -> Void
func handle(_ action: Action) {
handleClosure(action)
}
init<ActionHandlerType: ActionHandler>(_ actionHandler: ActionHandlerType) where ActionHandlerType.Action == Action {
handleClosure = actionHandler.handle(_:)
}
}
Now if there are more methods in ActionHandler
, you can just add it in this type, rather than adding them everywhere where you assigned to Foo.handler
. Foo
can just have
weak var handler: AnyActionHandler<Action>?
And assigning the handler looks like:
Foo().handler = AnyActionHandler(handler)
This AnyActionHandler
type is called a "type eraser".