Protocol and limiting protocol adoption

Ashwin Shrestha
5 min readApr 26, 2020

As I was just going through few Swift articles in medium, I came across this article titled All about protocols in Swift by fellow Swift programmer, where he had explained about Swift protocol, a decent article if I might say.

There in the comment section, someone had asked a question about limiting protocol adoption as below:

I have a question that is there any difference if I add constraint to the protocol itself, not to the protocol extensions?

protocol Attack where Self: Fighter {
func punch()
}

extension Attack {
func punch() { }
}

I thought maybe I can elaborate/ answer more on that question as it was unanswered for a very long time. First of all, the questioner said, “add constraint to the protocol itself”, it’s not called adding constraint but “limiting protocol adoption”.

Coming back to the question, let me answer the question with a code snippet

protocol ShakeAble where Self: UIView {
func shakeUIView()
}
extension ShakeAble where Self: UIImageView {

func shakeUIImageView() {
print("Shaking uiiimageview in extension")
}
}extension ShakeAble {

func shakeAnyThing() {
print("shaking anything which is subclass of UIView in extension")
}
}

Here in the above code snippet:

a. I have made a ShakeAble protocol with protocol adoption limited to UIView i.e. any class which inherits UIView or it’s subclasses like: UIButton, UIImageView etc can conform to this ShakeAble protocol and needs to implement the shakeUIView() method.

b. The protocol has been extended and has a method shakeUIImageView() with its default implementation, but this method can only be called by any class which is Subclass of UIImageView and conforms to ShakeAble protocol.

c. The protocol has another extension that has a method shakeAnyThing(), which can be called by any class which is Subclass of UIView (doesn’t need to be inherited from UIImageView) and conforms to ShakeAble protocol, but doesn’t necessarily implement the method.

class AppTextField: UITextField, ShakeAble {      func shakeUIView() {
print("shaking AppTextField")
}
}

Now as you can see, a class AppTextField which inherits from UITextField can conform to ShakeAble protocol, but it needs to implement the method shakeUIView() as there is no default implementation of it in any extension.

class ProductImageView: UIImageView, ShakeAble {      func shakeUIView() {
print("shaking ProductImageView")
}
}

Similarly here we can see, a class ProductImageView which inherits from UIImageView can conform to ShakeAble protocol, as ProductImageView class inherits UIImageView which is a subclass of UIView. so, we need to implement the method shakeUIView(), but it may or may not implement the shakeUIImageView() method and just call the shakeUIImageView() method in protocol’s extension.

Let’s make another class MyView which inherits UIView and conforms to ShakeAble protocol:

class MyUIView: UIView, ShakeAble {      func shakeUIView() {
print("called \(#function) at line: \(#line)")
}
}

So basically here, I conformed to ShakeAble protocol and added implementation for shakeUIView() method.

Now getting to the gist of this all blabber I have been doing here with some code examples:

  1. Making an instance of MyUIView and calling the methods
let view = MyUIView(frame: .zero)
view.shakeUIView()
view.shakeUIImageView()
view.shakeAnyThing()

What this above code snippet shows is, an instance of MyUIView can call, shakeUIView() and shakeAnything() methods but not shakeUIImageView(). This is because, though MyUIView has conformed to ShakeAble protocol, but the shakeUIImageView() in extension has explicitly limited protocol adoption for subclasses of UIImageView, though UIImageView is subclass of UIView. This is what limited protocol adoption is.

2. Making an instance of AppTextField and calling the methods

let view = AppTextField(frame: .zero)
view.shakeUIView()
view.shakeUIImageView()
view.shakeAnyThing()

The same is the case, which was pretty obvious. Now removing the method call, we try that again, and here is the result.

let view = AppTextField(frame: .zero)
view.shakeUIView()
view.shakeAnyThing()

If you can see here “shaking anything which is subclass of UIView in extension” is the result of view.shakeAnyThing(), which is obvious as we are calling the default implementation fo the method from the extension. Now if we add this method in our own class AppTextField, the result is different.

class AppTextField: UITextField, ShakeAble {      func shakeUIView() {
print("shaking AppTextField")
}
func shakeAnyThing() {
print("shaking anything which is subclass of UIView in AppTextField")
}
}let view = AppTextField(frame: .zero)
view.shakeUIView()
view.shakeAnyThing()

This shows that the default implementation in extension can be overridden if implemented in the class.

3. Now finally addressing the elephant in the room and our problem statement.

let view = ProductImageView(frame: .zero)
view.shakeUIView()
view.shakeUIImageView()
view.shakeAnyThing()

So what we can see here is, if we limit the protocol adoption to the protocol, not extension, this works fine and any class which is a subclass of the one which we used for protocol adoption limitation can conform and implement the methods.
If we limit the protocol adoption in extension, anything can conform that protocol and implement the methods but the methods inside the extension which is limited to certain protocol adoption can not be called or overridden to implement, in our case:

protocol ShakeAble {
func shakeUIView()
}
extension ShakeAble where Self: UIImageView { func shakeUIImageView() {
print("Shaking uiiimageview in extension")
}
}

If our protocol had no limitation to protocol adoption, any instance of any type could conform to ShakeAble and implement shakeUIView() (per se), but the instance if it was of UIImageView type, only then it could call that shakeUIImageView() method.

If you have any questions or suggestions, feel free to post them in the comment section. If you like the post, do clap and share it with your friends.

Thanks for reading.

Happy Coding 🙂

--

--