Tuesday 3 May 2016

Swiftly Becoming Confused About Protocols (Part II)






by Sarang Nagmote



Category - Mobile Apps Development
More Information & Updates Available at: http://vibranttechnologies.co.in




In the first part of this series, we covered how to use policies as good ol interfaces. Something were all used to. But Swift allows policies to do much more than that — lets take a look at using protocols as policies describing type behavior.

Protocols as Policies

Okay, so let’s take a different approach. What about using protocols as policies for generics? Generics provide us with a large degree of reuse as well, and they do it at compile time, not run time. All messages are statically dispatched, and overall, generics are more optimizable.
import Foundationimport XCPlaygroundXCPlaygroundPage.currentPage.needsIndefiniteExecution = truestruct TimerState { var elapsedSeconds = 0.0 var stopTimer = false var restartTimer = false}
This is the state of the timer. In the previous version, the state information was included in the Timer implementation class as object properties. In this version, we take the same variables, and wrap them into a structure. This structure would need to be exported from the module as a public type.
protocol Event { init(secondsElapsed: NSTimeInterval) var secondsElapsed: NSTimeInterval { get }}struct TimerEvent: Event { var _secondsElapsed = 0.0 init(secondsElapsed: NSTimeInterval) { _secondsElapsed = secondsElapsed } var secondsElapsed: NSTimeInterval{ get { return _secondsElapsed } }}
Here, we define the event protocol to constrain our generic and a simple implementation of the protocol. We don’t use the implementation of the protocol in our class whatsoever; we only use it when we instantiate the generic class.
func defaultFirePolicy<T: Hashable, S: Event>(myTimer: NSTimer,inout timerState: TimerState,timer: Timer<T, S>) -> Void { if timerState.stopTimer { myTimer.invalidate() timerState.stopTimer = false return } if timerState.restartTimer { timerState.elapsedSeconds = 0 timerState.restartTimer = false timer.start() } timerState.elapsedSeconds++ timer.observers.forEach { let ti = NSTimeInterval(timerState.elapsedSeconds) let event = S(secondsElapsed: ti) $1(event) } if timerState.elapsedSeconds >= 10 { myTimer.invalidate() }}
This is a default implementation for the callback handler. We define this so that users don’t need to define their own handler each time the instantiate this generic timer class.
final class Timer<T: Hashable, S> { var observers: [T: (S) -> Void] = [:] var interval: NSTimeInterval weak var timer: NSTimer? var timerState = TimerState() var firePolicy: (NSTimer, inout TimerState, Timer) -> Void init( interval: NSTimeInterval = 1.0, firePolicy: (NSTimer, inout TimerState, Timer<T, S>) -> Void = defaultFirePolicy ) { self.interval = interval self.firePolicy = firePolicy } func add(key: T, o:(S) -> Void) { observers[key] = o } func remove(key: T) { observers.removeValueForKey(key) } func start() { timer = NSTimer.scheduledTimerWithTimeInterval( interval, target: self, selector: Selector("timerFire:"), userInfo: nil, repeats: true ) } func stop() { timerState.stopTimer = true } func restart() { timerState.stopTimer = true timerState.restartTimer = true } dynamic func timerFire(myTimer: NSTimer) { firePolicy(myTimer, &timerState, self) }}
This is the generic timer. So what have we done here?
Well, we have a class that allows us to define the key type and the event type. We use the generic event type parameter to define the observer interface as well. The class itself is final, but we allow users to implement arbitrary customization within the timer callback. We do supply a default handler, via the defaultFirePolicy method, but that’s the only customization point we support. The implementation is final, and can’t be subclassed as a result.
var timer = Timer<String, TimerEvent>()timer.add("l1") {print("Seconds elapsed: ($0.secondsElapsed)")}timer.start()
This runs the timer with the default handler, creating this output:
Seconds elapsed: 1.0Seconds elapsed: 2.0Seconds elapsed: 3.0Seconds elapsed: 4.0Seconds elapsed: 5.0Seconds elapsed: 6.0Seconds elapsed: 7.0Seconds elapsed: 8.0Seconds elapsed: 9.0Seconds elapsed: 10.0
Okay, so we see how it works by default, how do we add a new handler? We can use a trailing closure for that, ending up with something that looks like this:
var timer = Timer<String, TimerEvent>() { print("...From closure handler...") if $1.stopTimer { $0.invalidate() $1.stopTimer = false return } if $1.restartTimer { $1.elapsedSeconds = 0 $1.restartTimer = false $2.start() } $1.elapsedSeconds++ var timerState = $1 $2.observers.forEach { let ti = NSTimeInterval(timerState.elapsedSeconds) let event = TimerEvent(secondsElapsed: ti) $1(event) } if $1.elapsedSeconds >= 10 { $0.invalidate() }}timer.add("l1") {print("Seconds elapsed: ($0.secondsElapsed)")}timer.start()
Here, we define a trailing closure when we create the timer. When run, this yields:
...From closure handler...Seconds elapsed: 1.0...From closure handler...Seconds elapsed: 2.0...From closure handler...Seconds elapsed: 3.0...From closure handler...Seconds elapsed: 4.0...From closure handler...Seconds elapsed: 5.0...From closure handler...Seconds elapsed: 6.0...From closure handler...Seconds elapsed: 7.0...From closure handler...Seconds elapsed: 8.0...From closure handler...Seconds elapsed: 9.0...From closure handler...Seconds elapsed: 10.0
This is a simple example. The key point here is that, with policy protocols, generics, and functional programming, we can design software in different ways that we’re used to, and that these new design capabilities have properties that we just didn’t have before when using interface protocols.
Now, I’m not sure which approach is better. Honestly, I think they really apply to different situations more than anything else. Do you need the kind of type abstraction interface protocols provide? Then use them for that. Do you need policy protocols for generics? Then use them. You need to be very careful how you do this though — the two approaches don’t seem mix very well.

No comments:

Post a Comment