Protocol and limiting protocol adoption
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:
- 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 🙂