Swift protocol extensions 102

Yesterday’s post was good. But what’s coming up now is even better.

The context

Imagine that, due to requirements, you need to have an insane amount of different classes (i.e views) that contain text labels. And you need to be able, due to requirements again, to customise some of those text labels. Not all of them, only some of them. And the funny thing is that you don’t even know which ones.

In other words, you need to provide a default behaviour that needs to be applied to an indeterminate amount of types, but that can be customised for some specific types.

Don’t you love being a software engineer?

The difficult solution

Sexy software engineers do not whine. Sexy software engineers roll their sleeves up, reach for the toolbox, consider all the tools available and implement the simplest thing that could possibly work. Or, like in this case, the simplest thing that could possibly work, while being scalable and maintainable enough.

And here is where protocol extensions com to the rescue again. This time, with a little twist: Generics.

First, we have our view. (for the shake of the example, let’s call it BaseClass)

class BaseClass {
    init() {
        print("init in base class")
    }
}

Now, we have two different subclasses. Both do the same (again, for the shake of the example, in real life those two subclasses may or may not create the same amount of labels)

class SubclassA: BaseClass {
    override init() {
        print("init in Subclass A")
        super.init()
        self.initLabels()
    }
    
    private func initLabels() {
        Label().titleLabel(self)
        Label().subtitleLabel(self)
    }
    
}

class SubclassB: BaseClass {
    override init() {
        print("init in subclass B")
        super.init()        
        self.initLabels()
    }
    
    private func initLabels() {
        Label().titleLabel(self)
        Label().subtitleLabel(self)
    }
}

What is it that we want? We want a certain behaviour to be the default.

So, again for the shake of the example, let’s say we have a Label:

final class Label {
    init() {
        //print("initializing a label")
    }
}

Here is the neat part. First we declare a Generic protocol:

protocol BlogLabel {
    typealias ItemType
    func titleLabel(parameter: ItemType)
    func subtitleLabel(parameter: ItemType)
}

We enforce Label to implement it, via an extension. Notice how we set the item type as the base class. Why the BaseClass? Because this would be the default behaviour applicable to all subclasses of BaseClass

extension Label: BlogLabel {
    typealias ItemType = BaseClass
    func titleLabel(parameter: ItemType) {
        print("calling titleLabel in default");
    }
    
    func subtitleLabel(parameter: ItemType) {
        print("calling subtitleLabel in default");
    }
}

Now, let’s assume that we want one of the methods in the protocol to have a specific implementation when the type passed to it is SubclassB. An specific extension will do it:

extension BlogLabel {
    func titleLabel(parameter: SubclassB) {
        print("calling titleLabel specific for subclassB");
    }
}

So, if we create an instance of SubclassA and another instance of SubclassB, the this would be the trace:

_ = SubclassA()
_ = SubclassB()

/////////
init in Subclass A
init in base class
calling titleLabel in default
calling subtitleLabel in default
init in subclass B
init in base class
calling titleLabel specific for subclassB
calling subtitleLabel in default

Final words

Protocol extensions are very powerful. We can use them to provide a default implementation of a protocol, and specialise that implementation for an specific type, either by applying the extension to types that comply to an specific condition (relying on the where clause) or by specifying the type of parameters that we want in the methods part of the protocol.

Leave a Reply

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