Skip to content

Instantly share code, notes, and snippets.

@anandabits
Last active March 1, 2022 23:45
Show Gist options
  • Save anandabits/ec26f67f682093cf18b170c21bcf433e to your computer and use it in GitHub Desktop.
Save anandabits/ec26f67f682093cf18b170c21bcf433e to your computer and use it in GitHub Desktop.
A minimalist responder chain implemented in pure Swift
// 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
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment