Brent Simmons has been discussing implementing a responder chain in Swift,
Joe Groff responded with a suggestion to use as? and a protocol to
implement this behaviour, so I thought I'd have a quick stab at it on the train
to work.
You can download a playground here, or view as a gist.
I created a ResponderProtocol with a nextResponder property,
a walkResponderChain function that takes an implementor of ResponderProtocol
and a callback which accepts an implementor of ResponderProtocol and
returns a Bool. The callback returns true if the responder
handles the event.
walkResponderChain recursivley checks responders until it finds one
that handles the event, or finds nil in a reponders nextResponder
variable.
protocol ResponderProtocol {
var nextResponder: ResponderProtocol? { get }
}
func walkResponderChain(firstResponder: ResponderProtocol?, handleEvent: (ResponderProtocol) -> Bool) -> ResponderProtocol? {
guard let responder = firstResponder else {
return nil
}
guard handleEvent(responder) == false else {
// Responder handled our call
return responder
}
return walkResponderChain(responder.nextResponder, handleEvent: handleEvent)
}
Now I can define CopyProtocol and a copy function. The copy
function calls walkResponderChain and checks each object to see if it
implements its required CopyProtocol, and if so calls instances copy
method, and returns true to tell walkResponderChain to stop checking
responders.
protocol CopyProtocol {
func copy()
}
func copy(firstResponder: ResponderProtocol) -> ResponderProtocol? {
return walkResponderChain(firstResponder) { responder in
if let validResponder = responder as? CopyProtocol {
validResponder.copy()
return true
}
return false
}
}
Here is the full source for the playground.
// MARK: Protocols
protocol ResponderProtocol {
var nextResponder: ResponderProtocol? { get }
}
protocol CopyProtocol {
func copy()
}
// MARK: Concrete responder
class Responder: ResponderProtocol {
var nextResponder: ResponderProtocol?
}
// MARK: Responder chain classes
class Button: Responder {}
class View: Responder {}
class CopyableView: Responder, CopyProtocol {
func copy() {
print("Copied copyable view")
}
}
class Window: Responder, CopyProtocol {
func copy() {
print("Copied window")
}
}
// MARK: Walk responder chain helper
func walkResponderChain(firstResponder: ResponderProtocol?, handleEvent: (ResponderProtocol) -> Bool) -> ResponderProtocol? {
guard let responder = firstResponder else {
return nil
}
guard handleEvent(responder) == false else {
// Responder handled our call
return responder
}
return walkResponderChain(responder.nextResponder, handleEvent: handleEvent)
}
// MARK: Copy via responder chain
func copy(firstResponder: ResponderProtocol) -> ResponderProtocol? {
return walkResponderChain(firstResponder) { responder in
if let validResponder = responder as? CopyProtocol {
validResponder.copy()
return true
}
return false
}
}
// Test case
let button = Button()
let view = View()
let copyView = CopyableView()
let window = Window()
// Setup responder chain as Button -> CopyableView -> Window
button.nextResponder = copyView
view.nextResponder = window
copyView.nextResponder = window
copy(button) // CopyableView
// Setup responder chain as Button -> View -> Window
button.nextResponder = view
copy(button) // Window