Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Swiftly Becoming Confused About Protocols (Part II)

DZone's Guide to

Swiftly Becoming Confused About Protocols (Part II)

How Swift can use policies in more interesting ways than just interfaces.

· Mobile Zone
Free Resource

Launching an app doesn’t need to be daunting. Whether you’re just getting started or need a refresher on mobile app testing best practices, this guide is your resource! Brought to you in partnership with Perfecto

In the first part of this series, we covered how to use policies as good ol' interfaces. Something we're all used to. But Swift allows policies to do much more than that — let's 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 Foundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

struct 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.0
Seconds elapsed: 2.0
Seconds elapsed: 3.0
Seconds elapsed: 4.0
Seconds elapsed: 5.0
Seconds elapsed: 6.0
Seconds elapsed: 7.0
Seconds elapsed: 8.0
Seconds elapsed: 9.0
Seconds 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.

Keep up with the latest DevTest Jargon with the latest Mobile DevTest Dictionary. Brought to you in partnership with Perfecto.

Topics:
swift ,ios ,apple ,mobile

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}