More on (protocol) extensions

Today I’ve found myself in what I would consider a funny situation: having to explain what protocol extensions are to an Android developer that has just started to get some exposure to Swift.

In fact, the whole concept of extensions (the good old categories in the not so good old Objective-C times) are a little bit difficult to grasp. So I decided to the best way to explain the whole thing would be to start from scratch, with one protocol, one extension, and a class implementing the protocol:

protocol Soundable {
    func noise() -> String
}

extension Soundable {
    func noise() -> String {
        return "Rawwwwwrrrrrrr!!!"
    }
}

final class Bear: Soundable {
    
}

let animal: Soundable = Bear()
print(animal.noise())

Neat!. Now, on to the next step: overriding the default implementation provided by the protocol extension:

final class Cat: Soundable {
    func noise() -> String {
        return "Meow"
    }
}

let anotherAnimal: Soundable = Cat()
print(anotherAnimal.noise())

Good. Now, we understand how protocol extensions work, how we can use them to add behaviour to entities, and how we can override that behaviour, when needed.

But there is more to it. Protocol extensions can also be applied to specific types. In particular, we can apply a protocol extension to one type and only one type that implements said protocol.

protocol Soundable {
    func noise() -> String
}

final class Bear {
    
}

extension Bear: Soundable {
    func noise() -> String {
        return "Rawwwwwrrrrrrr!!!"
    }
}

let animal: Soundable = Bear()
print(animal.noise())

final class Cat: Soundable {
    
}

extension Soundable where Self: Cat {
    func noise() -> String {
        return "Meow"
    }
}

let anotherAnimal: Soundable = Cat()
print(anotherAnimal.noise())

But there is more to it. Extensions can also enforce types to implement a protocol. So, we can modularise the previous example even more:

protocol Soundable {
    func noise() -> String
}

final class Bear {
    
}

extension Bear: Soundable {}

extension Soundable where Self: Bear {
    func noise() -> String {
        return "Rawwwwwrrrrrrr!!!"
    }
}

let animal: Soundable = Bear()
print(animal.noise())


final class Cat {
    
}

extension Cat: Soundable {}

extension Soundable where Self: Cat {
    func noise() -> String {
        return "Meow"
    }
}

let anotherAnimal: Soundable = Cat()
print(anotherAnimal.noise())

func testNoise(thing: Soundable) -> String {
    return thing.noise()
}

print(testNoise(animal))
print(testNoise(anotherAnimal))

And now we have two axis of customisation. We can force a type to implement a protocol, and we can change the implementation of the methods in that protocol according to the type that is implementing it.

And yes, Soundable, as a protocol name, is horrible.

Leave a Reply

Your email address will not be published. Required fields are marked *