|
// 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 |
|
*/ |