Subscribe now

Extensions & Protocols [11.10.2016]

protocol conformance does not have to be declared at the original definition of the type. They can be added in an extension.

  struct Person {
  }

  extension Person : Greeting {
    func sayHello(to name:String)->String {
      return "Hello, \(name)."
    }
  }

Here, we've created an extension on the Person struct, to add conformance to the Greeting protocol. Inside the extension, I provide an implementation of the required methods.

Any type can be extended, adding functions, and computed properties, whether they are needed by protocols or not.

  extension Person {
    var name:String
  }

This is a compiler error, because we cannot add stored properties in an extension.

  struct Person {
    var label:String
  }

Any properties must be computed.

  extension Person {
    var name:String {
      get {
        return label
      }
      set {
        label = newValue
    }
  }

Here, the name computed property redirects to the label stored property, but we did not explicitly add a conformance to the protocol. To do that, we could create another extension:

  extension Person : Namable {
  }

As long as the required properties and methods are implemented somewhere in the module (or an imported module), you can add the conformance.

extensions can also add operators required by a protocol. One such protocol is Equatable, you'll use it a lot. As a motivating example, I'm building a music app, which needs to precisely represent time as a fraction of beats. I created the BeatInterval struct to represent time.

  struct BeatInterval {
    let numerator:Int
    let denominator:Int
  /*  init(numerator:Int,  denominator:Int) {//I've left this as a comment to avoid writing the gcd function
      let gcd = GCD(numerator, denominator)
      self.numerator = numerator / gcd
      self.denominator = denominator / gcd
    }  */
  }

I need to periodically check whether my cursor is at the beginning or end of a note, so it makes sense that I need to be able to determine if two BeatIntervals are equal.

The Equatable protocol has a single requirement: a global method called == which takes two arguments of the same type and returns a Bool.

We’ll declare conformance to the protocol in an extension:

  extension BeatInterval : Equatable {
  }

But the actual operator function goes in the global scope, not in struct's scope:

  func ==(lhs: BeatInterval, rhs: BeatInterval)->Bool {

Of course, the protocol places no requirements on the names of the arguments, but its somewhat misleading that I don't need to write _ before them.

Counter example:

  func ==(_ lhs: BeatInterval, _ rhs: BeatInterval)->Bool {

Next, the guts of the function. I've left out the greatest common denominator function for clarity.

    //because the init algorithm does a GCD on the values, we can simply compare both members
    return lhs.numerator == rhs.numerator && lhs.denominator == rhs.denominator
  }

This is your first exposure to overloading operators in Swift, so take a moment for a breath. Operator overloads are static global functions. The function's name is the operator. The argument names are not important, because we're never going to write them at the call site.

Notice that we used a struct as an example for ==, because classes use reference equality, ===, we don't usually need to declare an == for a class.

Protocol Extensions

You may be wondering, cool, now we need to define `!=`, right?. Uh... Not exactly. See, Swift lets us cheat. The creators of the Equatable protocol declared a != operator and then wrote the code for it, and we don't need to do that ourselves. That's called a default implementation.

Suppose we want to define our own default implementation? Yep. Here's where protocols in Swift suddenly get massively powerful. We can use them to fake multiple inheritance. Remember that Namable protocol defined a name property?

  import Foundation

  protocol Namable {
    var name:String { get set }
  }

  extension Namable {
    var firstName:String {
      return name.components(separatedBy: " ")[0]
    }
  }

I've used an extension on Namable to add another method, firstName. It's written as though it's just any other method on some type named Namable, and references its property, name.

Now, any type which conforms to Namable will get the firstName:String computed property!

  class Person : Namable {
    var name:String
    init(name:String) {
      self.name = name
    }
  }

  var johnny:Person = Person(name: "Johnny Appleseed")
  johnny.firstName  //Johnny

Now, that's amazing, everything that conforms to a protocol can gain functionality. These extensions can add computed properties and methods.

Default implementations

But wait, there's more! Protocol extensions support default behaviors. Suppose I add a declaration of the firstName property to the original protocol definition:

  protocol Namable {
    var name:String { get set }
    var firstName:String { get }
  }

And now, not only does my class get the behavior defined in the protocol extension, but if I choose to, it can replace it.

  class Person {
    var name:String
    init(name:String) {
      self.name = name
    }
    var firstName:String {
      return name.components(separatedBy: " ").last!
    }
  }

  var person:Person = Person(name: "Johnny Appleseed")
  person.firstName  //Appleseed

This override gives me the last name component, just so you can see that it is in fact working. Cool, so I can provide a default implementation when I define the protocol, but allow space for my conforming types to override it if they need to.

Now, here's the problem. When you are defining a class, if you do not override the behavior in the originally-adopting class, you cannot effectively override it in the subclass. The method or property will be dispatched on the type the code declares it as. Confusing right, huh?

Here's an example; in Star Trek, Bajorans are an alien who list their family name first, so Kira Nerys has a family name of Kira. So imagine Person has a subclass, Bajoran:

  class Person : Namable {
    var name:String
    init(name:String) {
      self.name = name
    }
  }

  class Bajoran : Person {
    var firstName:String {
      return name.components(separatedBy: " ").last!
    }
  }

As long as I declare my variable as a Bajoran, I'll get my intended behavior:

  let person:Bajoran = Bajoran(name:"Kira Nerys")
  person.firstName  //"Nerys"

But as soon as I declare the type as the protocol, it's not going to work right anymore.

  let person:Namable = Bajoran(name:"Kira Nerys")
  person.firstName  //"Kira"

To make this work, I have to remember to implement the method in Bajoran's originally adopting class, Person.

  var name:String
    init(name:String) {
      self.name = name
    }
    var firstName:String {
      return name.components(separatedBy: " ").first!
    }
  }
  class Bajoran : Person {
    override var firstName:String {
      return name.components(separatedBy: " ").last!
    }
  }

  let person:Namable = Bajoran(name:"Kira Nerys")
  person.firstName  //"Nerys"

Now I get my overrides as I hoped, but it came at the price of having to repeat the default implementation in my subclass. And don't try casting self as the protocol:

  class Person : Namable {
    ...
    var firstName:String {
      return (self as Namable).firstName
    }
    ...

It'll just go for infinite recursion.

Given that working with protocols means you code will generally not know the exact type, and the whole point of subclassing is to extend or override behavior from the superclass, I think this is a poorly-designed feature of Swift. I suggest you avoid it. Only use protocol extensions where it doesn't make sense to override the behavior, like defining the != operator for any type that defines ==.

Summary

Today we learned how to add computed properties, functions and even protocol conformance to any type using an extension. We learned how to add default implementations in extensions, and how how to replace them in a conforming type, and when we can't override them.

Default protocol implementations, now that's Swift!