struct Post: BlogDelegate

Table of Contents

A couple days into school, we covered delegates and protocols. I definitely saw some fellow students struggle with the concept. I wish I could say I have a perfect metaphor to help with the understanding, but my comparison ultimately relies on having some existing understanding of common software development patterns.

Classes

I’d first like to briefly illustrate how classes work to give us something to compare against.

In a typical superclass/subclass model, the superclass is defined with various properties and methods that a subclass then inherits in their entirety.

Subclassing Example

In this example, we will create some basic classes representing some things on a farm.

// superclass

class FarmObject {
    let weight: Int
    init(weight: Int) {
        self.weight = weight
    }
}

// subclasses
class FarmVehicle: FarmObject {
    let wheelCount: Int
    let soundItMakes: String
    let horsePower: Int

    init(weight: Int, wheelCount: Int, soundItMakes: String, horsePower: Int) {
        self.wheelCount = wheelCount
        self.soundItMakes = soundItMakes
        self.horsePower = horsePower
        super.init(weight: weight)
    }

    func makeNoise() {
        print(soundItMakes)
    }
}

class FarmAnimal: FarmObject {
    let legCount: Int
    let soundItMakes: String
    let isAHorse: Bool // for the luls - would actually be smarter to make a horse subclass :P
    let name: String

    init(weight: Int, name: String, legCount: Int, soundItMakes: String, isAHorse: Bool) {
        self.legCount = legCount
        self.soundItMakes = soundItMakes
        self.isAHorse = isAHorse
        self.name = name
        super.init(weight: weight)
    }

    func makeNoise() {
        print("\(name) goes \"\(soundItMakes)!\"")
    }
}

class FarmBuilding: FarmObject {
    let numberOfWalls: Int
    let name: String
    init(weight: Int, name: String, numberOfWalls: Int) {
        self.numberOfWalls = numberOfWalls
        self.name = name
        super.init(weight: weight)
    }
}

class Horse: FarmAnimal {
    init(weight: Int) {
        super.init(weight: weight, name: "Sparkles", legCount: 4, soundItMakes: "Naaay", isAHorse: true)
    }
}

let myPony = Horse(weight: 900)
myPony.makeNoise()

In this example, all three subclasses inherited the weight property definition as well as everything else they defined independently. However, I want you to notice how both FarmAnimal and FarmVehicle have to each define soundItMakes and makeNoise(), effectively duplicating our code. It wouldn’t make sense to have those on a FarmBuilding because buildings don’t make sounds. However, it also doesn’t work to put the property on the superclass FarmObject because, even though FarmAnimal and FarmVehicle would stop duplicating their code, FarmBuilding we would still result in having this strange functionality.

In the same vein, it makes sense for both a FarmAnimal and FarmBuilding to have names (you might refer to different buildings as "house" or "barn", but yeah it’s a bit of a stretch) but it’s not as common to have names for a FarmVehicle.

The primary takeaway I want you to understand is that the inheritance in inherently linear with subclassing.

Protocols

Next I’d like to explain protocols. I’ll skip the formal definition and go right to the comparison. I like to think of protocols as the next evolution of subclassing.

Protocol Example

Using the previous example as a starting place, I’ll reorganize the setup to use protocols instead.

protocol FarmObject {
    var weight: Int { get }
}

protocol FarmNoiseMaker {
    var soundItMakes: String { get }
    func makeNoise()
}

extension FarmNoiseMaker {
    func makeNoise() {
        print(soundItMakes)
    }
}

protocol Identifiable {
    var name: String { get }
}

protocol FarmVehicle: FarmObject, FarmNoiseMaker {
    var wheelCount: Int { get }
    var horsePower: Int { get }
}

protocol FarmAnimal: FarmObject, FarmNoiseMaker, Identifiable {
    var legCount: Int { get }
    var isAHorse: Bool { get }
}

protocol FarmBuilding: FarmObject, Identifiable {
    var wallCount: Int { get }
}

At first glace, this looks greatly simplified, but also strange, if you’re not familiar with protocols. If you’re just getting started, feel free to skip over the advanced section (or come back when you’re more comfortable with the concept!).

But with those changes, now we can do

struct Horse: FarmAnimal {
    let legCount: Int
    let isAHorse: Bool
    let weight: Int
    let soundItMakes: String
}

let myPony = Horse(name: "Sparkles", legCount: 4, isAHorse: true, weight: 900, soundItMakes: "Naaaaaay!")
myPony.makeNoise() // prints "Naaaaaay!"

The biggest thing to take away here is that protocols act more like tags in metadata. Essentially, you can pick and choose protocols to conform to (aka inherit from) and skip over the ones that are irrelevant to the object type. Any class or struct you tag with a protocol now inherits everything the protocol has to offer, but simultaneously doesn’t require doing so linearly! See how for both FarmVehicle and FarmAnimal we conform to not only FarmObject, but also FarmNoiseMaker? Additionally, as long as you don’t tell your protocol to only work with class types, it can even work with structs. But at the very least, you should see that we can save ourselves a lot of boilerplate code!

If it’s not obvious, this is extremely powerful.

Procotols Advanced

The additional concepts here are

  • Protocol Extensions

    • Nothing is optional in protocol implementation. If a protocol declares that it needs a function or property, anything conforming to it is REQUIRED to implement those details. However, Protocol Extensions can provide default implementations for functions, even using the very properties the protocol defines! The example above uses the protocol extension for makeNoise(), but we can easily define our own functionality by writing the struct like this:

      struct Horse: FarmAnimal {
          let name: String
          let legCount: Int
          let isAHorse: Bool
          let weight: Int
          let soundItMakes: String
      
          func makeNoise() {
              print("\(name) says: \(soundItMakes)")
          }
      }
      
      let myPony = Horse(name: "Sparkles", legCount: 4, isAHorse: true, weight: 900, soundItMakes: "Naaaaaay!")
      myPony.makeNoise() // now prints "Sparkles says: Naaaaaay!"
    • If the extension didn’t exist, we’d be required to write the makeNoise() function
  • Protocol Syntax
    • note the following things:
      • the lack of override in the function above
      • that everything defined in a protocol is a var and NOT a let
        • BUT you can conform to the protocol by declaring a let in place of the var, as long as the var is { get } only! (as opposed to { get set })
  • Protocol Composition
    • you may combine one or more protocols to create a new one! This was how FarmVehicle and FarmAnimal were created!

Delegates

Finally, delegates. Honestly, if you understand protocols, you already understand delegates. Delegates are just a certain type of protocol, just used in a consistent pattern, typically in a callback or data getting kind of idea.

Leave a Reply