A minimalist responder chain implemented in pure Swift

10 min read Original article ↗
// Created by Matthew Johnson on 5/28/16. // Copyright © 2016 Anandabits LLC. All rights reserved. // // This is a minimalist implementation of a responder chain in pure Swift. // // It is not intended to demonstrate the best way to // implement event processing in Swift. // // The intent is to show how little code is necessary to acheive behavior // similar to Cocoa's responder chain in pure Swift. // // There is not not a switch statement or dispatch table anywhere in this code. // // This example focuses on sending actions up the responder chain. // Actions were chosen because they are open to extension by users. // Touch, mouse, and keyboard event processing could also be implemented // using similar techniques with protocols such as `TouchHandler`, etc. // Instead of using a selector directly, messages are reified as instances // of types conforming to the `Message` protocol. // `Message` types should have value semantics. // // Each `Message` knows how to send itself to a `Handler`. // `Handler` will usually be a protocol, but this is not required. // // Defining the `Message` type and `Handler` protocol is a bit more work // than just using a selector. // // The benefits of that additional work are: // * Static conformance checking for `Handler` types. // * The opportunity to attach any data we wish to the event. // * No need to dynamically check the types of arguments handler. public protocol Message { associatedtype Handler // `sendToHandler` is intended to be called // by framework code. // User code would usually only implement this // in custom `Message` types. func sendToHandler(_ handler: Handler) } // `Responder` types are only required to provide a `nextResponder` getter. // They are not required to actually handle any messages // directly or through the responder chain. public protocol Responder { var nextResponder: Responder? { get } } // `Responder` types should not implement `tryToHandle`. // It is an extension method in order to ensure correct // (framework defined) dispatch always happens. // // An alternative design could make this a defualt implementation // of a `tryToHandle` requirement in the protocol // and provide a way to call the framework-provided // implementation if necessary. public extension Responder { func tryToHandle<MessageType: Message>(_ message: MessageType) -> Bool { return message.tryToSendTo(self) } func canHandle<MessageType: Message>(_ message: MessageType) -> Bool { return message.canSendTo(self) } func tryToHandle<ActionType: Action>(_ action: ActionType, fromSender sender: ActionType.Sender) -> Bool { return action.sendFrom(sender, to: self) } } // `Action` types are like `Message` types but the `sender` // is provoded to the action when it is asked to send itself // to the `Handler`. // Actions will usually forward the `sender` along to their // `Handler` but that is not required. // `Action` types should have value semantics. public protocol Action { associatedtype Sender associatedtype Handler // `performWithHandler` is intended to be called // by framework code. // User code would usually only implement this // in custom `Action` types. func performWithHandler(_ handler: Handler, sender: Sender) } // Type erased wrapper. // This won't be necessary when Swift has generalized existentials public struct AnyAction<Sender> { private let action: AnyActionBase<Sender> init<ActionType: Action where ActionType.Sender == Sender>(_ action: ActionType) { self.action = AnyActionWrapper(action) } } // `Control` is a minimal protocol for target action style event handling. // It is relatively straightforward to extend it to support // multiple target action pairs, different control events, etc. // In a future version of Swift it might also be possible for // `Control` to be a mixin and provide its own storage for // `target` and `action`. public protocol Control { var target: Responder? { get } var action: AnyAction<Self> { get } } // `Control` types should not implement `performAction`. public extension Control { func performAction() { let responder = target ?? lookupCurrentFirstResponder() action.sendFrom(self, to: responder) } } // `Button` is a very simple concrete `Control` used to // demonatrate how to use `Control`. public final class Button: Control { public var target: Responder? public var action: AnyAction<Button> public init<ActionType: Action where ActionType.Sender == Button>(target: Responder?, action: ActionType) { self.target = target self.action = AnyAction(action) } public func click() { performAction() } } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // // // Everything above this is the public interface // // // // Everything below this is private implementation // // // ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // The following private extensions are used by `Responder` // to dispatch through the `Message` and by `Control` // to dispatch through the `Action`. // This is necessary because only concrete types conforming tp // `Message` and `Action` actually know how to call the correct // method on the `Handler`. private extension Message { func tryToSendTo(_ firstResponder: Responder) -> Bool { guard let handler: Handler = findHandlerInChainStartingWith(firstResponder) else { return false } sendToHandler(handler) return true } func canSendTo(_ firstResponder: Responder) -> Bool { let handler = findHandlerInChainStartingWith(firstResponder) as Handler? return handler != nil } } private extension Action { func sendFrom(_ sender: Sender, to responder: Responder) -> Bool { guard let handler: Handler = findHandlerInChainStartingWith(responder) else { return false } performWithHandler(handler, sender: sender) return true } } private extension AnyAction { func sendFrom(_ sender: Sender, to firstResponder: Responder) -> Bool { return action.sendFrom(sender, to: firstResponder) } } // This implements the handler lookup loop that looks for // an appropriate `Handler` in the `Responder` chain. // // The example only shows one dispatch algorithm. // Alternative dispatch algorithms could be implemented over // any data structure containing `Responder` instances // allowing for arbitrarily complex dispatch logic. private func findHandlerInChainStartingWith<Handler>(_ firstResponder: Responder) -> Handler? { var nextResponder: Responder? = firstResponder while let responder = nextResponder { if let handler = responder as? Handler { return handler } print("\(responder) cannot handle the message") nextResponder = responder.nextResponder } return nil } // `firstResponder` is defined as a variable in user code // near the bottom of this file. // In a real framework it would be tracked by the framework // as part of the application state. private func lookupCurrentFirstResponder() -> Responder { return firstResponder } // These types only exist to perform type erasure for `AnyAction`. // When Swift introduces generalized existentials they will not // be necessary. /* abstract */ private class AnyActionBase<Sender> { func sendFrom(_ sender: Sender, to firstResponder: Responder) -> Bool { fatalError("abstract method AnyMessageBase.sendFrom:to: called") } } private final class AnyActionWrapper<ActionType: Action>: AnyActionBase<ActionType.Sender> { let action: ActionType init(_ action: ActionType) { self.action = action } override func sendFrom(_ sender: ActionType.Sender, to firstResponder: Responder) -> Bool { return action.sendFrom(sender, to: firstResponder) } } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // // // Everything above this is framework level code // // // // Everything below this is application level code // // // ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // A simple action message only requires a protocol // and a function that dispatches to the handler. protocol Fooable { func foo() } struct FooAction: Message { func sendToHandler(_ handler: Fooable) { handler.foo() } } // More complex actions are also possible. // These capture data and forward it to the handler // with full static type checking in force. // This is much better than needing to use the same // signature (or limited set of signatures) for all actions. // It avoids the need to make assertions about and / or cast // the arguments in the way that is necessary in Cocoa. // See: http://blog.wilshipley.com/2016/05/pimp-my-code-book-2-swift-and-dynamism.html // for an example of this assertion problem. protocol Barable { func bar(arg1: String, arg2: Int, arg3: Bool, arg4: Double) } struct BarAction: Message { var arg1: String, arg2: Int, arg3: Bool, arg4: Double func sendToHandler(_ handler: Barable) { handler.bar(arg1: arg1, arg2: arg2, arg3: arg3, arg4: arg4) } } // A basic `Responder` that doesn't handle any messages. class BasicResponder: Responder { var nextResponder: Responder? init(nextResponder: Responder? = nil) { self.nextResponder = nextResponder } } // `Responder` types that wish to handle messages are only // required to conform to the `Handler` protocol associated with // the `Message` types they wish to handle. // // The conformance is statically verified. // The handler method receives statically typed arguments // avoiding the need for any assertions and / or casts. class FooResponder: Responder, Fooable { var message: String var nextResponder: Responder? init(message: String, nextResponder: Responder? = nil) { self.message = message self.nextResponder = nextResponder } func foo() { print(message) } } // Put together a simple responder chain: let topLevelResponder = BasicResponder() let fooResponder = FooResponder(message: "FooResponder got it!", nextResponder: topLevelResponder) let firstResponder = BasicResponder(nextResponder: fooResponder) // Query the responder chain to find out that a `FiiAction` can be handled // by the current dynamic responder chain. let fooCanBeHandled = firstResponder.canHandle(FooAction()) print(fooCanBeHandled) /* BasicResponder cannot handle the message true */ // Send a `FooAction` and observe that it is handled by // the current dynamic responder chain. let fooHandled = firstResponder.tryToHandle(FooAction()) print(fooHandled) /* BasicResponder cannot handle the message FooResponder got it! true */ // Query the responder chain to find out that a `BarAction` *cannot* be handled // by the current dynamic responder chain. let barCanBeHandled = firstResponder.canHandle(BarAction(arg1: "arg", arg2: 42, arg3: true, arg4: 42)) print(barCanBeHandled) /* BasicResponder cannot handle the message FooResponder cannot handle the message BasicResponder cannot handle the message false */ // Try to send a `BarAction` even though it can't be handled by the current // dynamic responder chain. let barHandled = firstResponder.tryToHandle(BarAction(arg1: "arg", arg2: 42, arg3: true, arg4: 42)) print(barHandled) /* BasicResponder cannot handle the event FooResponder cannot handle the event BasicResponder cannot handle the event false */ // The custom `Handler` protocol and `Action` struct // are necessary boilerplate that is not required by Cocoa. // However, it is easy to imagine a future version of Swift // where they can be automatically synthesized during compilation // with an annotation as concise as `@IBAction`. // It is even possible that we may be able to implement synthesis-invoking // annotations like this in user-defined code (or some similarly concise syntax). protocol CustomActionable { func customAction(sender: Button) } struct CustomAction: Action { func performWithHandler(_ handler: CustomActionable, sender: Button) { handler.customAction(sender: sender) } } class CustomView: Responder, CustomActionable { var nextResponder: Responder? var button: Button init(sendActionToResponderChain: Bool = false) { button = Button(target: nil, action: CustomAction()) button.target = sendActionToResponderChain ? nil : self } // Custom, strongly typed action method. // See: http://blog.wilshipley.com/2016/05/pimp-my-code-book-2-swift-and-dynamism.html // to understand why the strong typing here is an important step forward. // // In a future version of Swift it might be possible to invoke // synthesis of an anonymous `Handler` protocol and `Action` struct // using the annotation we are already using in our code today. // @IBAction func customAction(sender: Button) { print("\(sender) sent message to \(self)") } } // Create an instance of the view and simulate a button click. let view = CustomView() view.button.click() /* Button sent message to CustomView */ // Create an instance of the view that sends the click action up // the responder chain. let upResponderChain = CustomView(sendActionToResponderChain: true) upResponderChain.button.click() /* Platinum.BasicResponder cannot handle the message Platinum.FooResponder cannot handle the message Platinum.BasicResponder cannot handle the message */