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

Swiftly Becoming Confused About Protocols, Part I: Interfaces

DZone's Guide to

Swiftly Becoming Confused About Protocols, Part I: Interfaces

Swift is popular, powerful, and (sometimes) confusing. Follow Chris Lamb as he connects the how and why you should use primitives.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

As I’m sure lots of you have noticed by now, protocols in Swift are powerful but confusing. With the addition of generics and associated types, the proper way to use protocols is really up in the air these days. It looks like we have two distinct ways we can use protocols. We can either use protocols as interfaces, or we can use protocols as policies on generic types. When you throw in Swift’s functional capabilities, really, what’s the right way to design software?

Well, I’m not sure what the right way to design your software is, or really mine at this point, but I’m getting better at using these primitives, and I thought I’d share what I’m doing with the rest of you. It’s really hard to find good guidance on how to use these things today; there’s plenty of information on how things work, but little on why you want to use them. I’d like to start to address that gap today.

So while these are issues I deal with when designing and writing software, nothing that I’ve coded has been around long enough to really understand the long-term implications of specific design choices. I’m not going to focus on judging which approach is the best ever to use, but I am going to try to address, based on my experience, when and why certain approaches are useful.

Protocols as Interfaces

First, as a canonical example, we’re going to develop a timer. The timer needs to be decoupled from any app it’s used in, and I don’t want to expose any native components (e.g. NSTimer). The timer will fire events to observers at a user-defined interval. First, let’s use protocol-as-interface design to solve the problem. Let’s get to playground-ing.

import Foundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true


Alright, so the above is pretty important whenever you’re doing anything that uses external threading. Playgrounds terminate whenever the top-level code finishes executing by default. In order to keep in running to receive callbacks from an external thread (in this case an instance of NSTImer), we need to configure the page to execute indefinitely. This means you’ll likely want to add a default killswitch at the end of the NSTimer callback (you’ll see this later). The basic system protocols, defined with explicit typing, and used as interfaces and types:

protocol Event {
init(secondsElapsed: NSTimeInterval)
var secondsElapsed: NSTimeInterval { get }
}

protocol Observer {
mutating func add(key: String, observer: (Event) -> Void)
mutating func remove(key: String)
}

protocol Control {
mutating func start()
mutating func stop()
mutating func restart()
}

protocol Timer: Observer, Control {}


Here, we define a data protocol first, the Event protocol. This passes information via the observer pattern to any registered observers. We then define a functional protocol to define observers. This interface expects a name for any registered observer as well as a function to associate with that name for a callback. Finally, we define a control protocol, and the timer protocol, which is defined as a composed type from the Observer and Control protocols.

The implementation class implements the Timer protocol:

class BasicTimer: Timer {
    weak var timer: NSTimer?
    var observers: [String: (Event) -> Void] = [:]
    var interval: NSTimeInterval
    var stopTimer = false
    var restartTimer = false
    var elapsedIntervals = 0

    init(interval: NSTimeInterval = 1.0) {
        self.interval = interval
    }

    func add(key: String, observer: (Event) -> Void) {
        observers[key] = observer
    }

    func remove(key: String) {
        observers.removeValueForKey(key)
    }

    func start() {
        timer = NSTimer.scheduledTimerWithTimeInterval(
          interval,
          target: self,
          selector: Selector("timerFire:"),
          userInfo: nil,
          repeats: true
        )
    }

    func stop() {
        stopTimer = true
    }

    func restart() {
        stopTimer = true
        restartTimer = true
    }

    dynamic func timerFire(myTimer: NSTimer) {
        if stopTimer {
            myTimer.invalidate()
            stopTimer = false
            return
        }

        if restartTimer {
            elapsedIntervals = 0
            restartTimer = false
            start()
        }

        elapsedIntervals++

        observers.forEach {
            let ti = NSTimeInterval(elapsedIntervals)
            let event = TimeEvent(secondsElapsed: ti)
            $1(event)
        }

        if elapsedIntervals >= 10 {
            myTimer.invalidate()
        }
    }
}


This implementation creates a new timer on start, and maintains the state information in a couple of boolean and an int variable, tracking the runstate of the timer and the number of elapsed intervals. Note that the timer callback method is defined as dynamic so it can receive Objective C messages via NSTimer.

func createTimer(interval: NSTimeInterval = 1.0) -> Timer {
  return BasicTimer(interval: interval)
}


If we’re using protocols-as-interfaces techniques, we’ll need to use a factory to create concrete objects.

var timer = createTimer()
timer.add("l1") {
print("Seconds elapsed: \($0.secondsElapsed)")
}
timer.start()


Finally, we create a timer, register an observer, and start the timer. We should see this emitted from the observer:

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


So let’s take a look at what we have here. So, first, we’re really defined the system via a group of protocols prior to developing a thing. We have our Event protocol, we have Observers and Control protocols, and we have a Timer protocol. This gives us lots of flexibility with regard to how we implement the system, and what we do with the developed components. For example, we can develop however many different implementations of Timer as we need, and we can submit all those different implementations to a manager that handles groups of timers. This manager may then implement the Control protocol, and can exert control over all the registered timers (or other Control conformant objects). We can easily extend the factory function to implement different Timers too. If they’d like, users can subclass BasicTimer too, and extend that to implement their own functionality pretty easily. That’s a ton of design power.

It’s also really unlimited. This can be bad. For example, you can certainly subclass Timer, but if you do, you don’t get to subclass anything else. This could be a problem - Timer doesn’t inherit from NSObject, and if your timer implementation needs to, you’re out of luck inheriting Timer. Also, if you inherit from Timer, you get all of Timer, whether you want it or not. And you’ll need to worry about proper Timer initialization from your subclass too. Hmmm - maybe inheriting from Timer isn’t such a great idea after all.

Allowing arbitrary protocol implementations may not be so great either. Essentially, you’ve defined everything in the system as a possible customization point. Though very flexible, it can also be very error prone and confusing. Finally, don’t forget, all this polymorphism is dynamic and happens at runtime, making it less optimizable.

That's it for the first part. In the second part of this series, we're going to look at how we can use protocols as type policies.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
swift ,mobile ,ios ,apple

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}