Created
June 3, 2016 21:49
-
-
Save roop/397e4cc5e7deeab6a896ef4034a06ae5 to your computer and use it in GitHub Desktop.
A responder chain implementation for a hypothetical pure-Swift equivalent for UIKit. More info at: http://roopc.net/posts/2016/swifty-responder-chain/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* A responder chain implementation for a hypothetical pure-Swift | |
* equivalent for UIKit. | |
* More info at: http://roopc.net/posts/2016/swifty-responder-chain/ | |
*/ | |
/* A responder is something that has an optional next responder, | |
so that we can have a chain of responders. */ | |
protocol Responder { | |
var nextResponder: Responder? { get } | |
} | |
/* A command needs to be able to define methods that take in the | |
instances of the corresponding responder sub-type as an | |
argument, so we need to set the corresponding responder type | |
as an associated type. */ | |
protocol Command { | |
associatedtype AssociatedResponder | |
func canPerformOnResponder(responder: AssociatedResponder) -> Bool | |
func performOnResponder(responder: AssociatedResponder) | |
} | |
/* To enable views to be included in the responder chain, you can | |
extend Responder and return whatever is appropriate as the | |
next responder. */ | |
class View { | |
var superview: View? = nil | |
} | |
extension View: Responder { | |
var nextResponder: Responder? { return self.superview } | |
} | |
/* The application knows what the first responder is, and it can | |
traverse the responder chain querying each responder in the | |
process. */ | |
class Application { | |
var firstResponder: Responder? = nil | |
func performCommand<C: Command>(command: C) { | |
var r: Responder? = self.firstResponder | |
while r != nil { | |
if let r = r { | |
if command.canPerformOnResponder(r) { | |
command.performOnResponder(r) | |
return | |
} | |
} | |
r = r?.nextResponder | |
} | |
} | |
} | |
/* The calls to canPerformOnResponder() and performOnResponder() | |
above pass a Responder argument, but the Command protocol only | |
declares methods that take in AssociatedResponder instances. | |
So to make the above code work, we need to generalize those | |
methods to take in any Responder. */ | |
extension Command { | |
func canPerformOnResponder(responder: Responder) -> Bool { | |
if let associatedResponder = responder as? AssociatedResponder { | |
return self.canPerformOnResponder(associatedResponder) | |
} | |
return false | |
} | |
func performOnResponder(responder: Responder) { | |
if let associatedResponder = responder as? AssociatedResponder { | |
self.performOnResponder(associatedResponder) | |
} | |
} | |
} | |
/* Now, we can start defining commands. */ | |
// Copy | |
protocol CopyResponder: Responder { | |
func canCopy() -> Bool | |
func copy() | |
} | |
struct CopyCommand: Command { | |
func canPerformOnResponder(responder: CopyResponder) -> Bool { | |
return responder.canCopy() | |
} | |
func performOnResponder(responder: CopyResponder) { | |
responder.copy() | |
} | |
} | |
// Go Fishing | |
protocol GoFishingResponder: Responder { | |
func canGoFishing() -> Bool | |
func goFishing() | |
} | |
struct GoFishingCommand: Command { | |
func canPerformOnResponder(responder: GoFishingResponder) -> Bool { | |
return responder.canGoFishing() | |
} | |
func performOnResponder(responder: GoFishingResponder) { | |
responder.goFishing() | |
} | |
} | |
/* We can make views respond to these commands. */ | |
class MyView: View { | |
let name: String | |
var isCopyable = false | |
var hasFishingLine = false | |
init(name: String) { | |
self.name = name | |
} | |
} | |
extension MyView: CopyResponder { | |
func canCopy() -> Bool { | |
return self.isCopyable | |
} | |
func copy() { | |
print("\(self.name) copying") | |
} | |
} | |
extension MyView: GoFishingResponder { | |
func canGoFishing() -> Bool { | |
return self.hasFishingLine | |
} | |
func goFishing() { | |
print("\(self.name) going fishing") | |
} | |
} | |
/* We can wire up views into a responder chain and | |
check how commands propagate through the chain */ | |
let topView = MyView(name: "topView") | |
let midView = MyView(name: "midView") | |
midView.superview = topView | |
let lowView = MyView(name: "lowView") | |
lowView.superview = midView | |
let app = Application() | |
app.firstResponder = lowView | |
midView.isCopyable = false | |
midView.hasFishingLine = true | |
topView.isCopyable = true | |
topView.hasFishingLine = true | |
app.performCommand(CopyCommand()) // "topView copying" | |
app.performCommand(GoFishingCommand()) // "midView going fishing" | |
midView.isCopyable = true | |
midView.hasFishingLine = false | |
topView.isCopyable = true | |
topView.hasFishingLine = true | |
app.performCommand(CopyCommand()) // "midView copying" | |
app.performCommand(GoFishingCommand()) // "topView going fishing" | |
/* The End */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The advantages (if any) are at least doubtful, but using the
SequenceType
:performCommand
can be "simplified" to: