Getting Hurt With Swift Protocol Extensions & Default Parameter Values
Of course, Swift protocol extensions and default parameter values are great features. And they are always safe, aren’t they? Well, not really.
Overview
In this article, I’ll show you how to get hurt using protocol extensions and default parameter values together. If you don’t know well these two features, no worries, I’ll explain them, briefly, in the first two paragraphs—feel free to skip them if you know well the subjects.
After the explanation of these two features, I’ll introduce the threat step by step with some examples. In the end, I’ll provide some suggestions to remove it. Happy Reading.
Protocol Extensions
Protocols can be extended to provide method and property implementations to conforming types. This allows you to define behavior on protocols themselves, rather than in each type’s individual conformance or in a global function.
We have a protocol APIRequestProtocol
, which contains a method request
and the members baseUrl
and query
. Then, we create two classes, UsersAPIRequest
and GroupsAPIRequest
, to get the users and groups data from an API request:
|
|
You can notice that both classes have the same value for the member baseUrl
and the same implementation for the method request
. To get rid of this duplication of code, we can use Protocol Extensions:
|
|
After the refactor, both classes use the default implementation inside extension APIRequestProtocol
.
In this way, if the compiler doesn’t find an APIRequestProtocol
implementation inside UsersAPIRequest
/GroupsAPIRequest
, it will be able to use the implementation inside the protocol extension.
Default Parameter Values
You can define a default value for any parameter in a function by assigning a value to the parameter after that parameter’s type. If a default value is defined, you can omit that parameter when calling the function.
We have a class View
, which has a constructor, init
, to set its background color. Then, we initialize 4 View
objects using its constructor:
|
|
You can notice that, most of the time, we set a background color .clear
. Instead of using every time .clear
as argument, we can assign a default parameter to backgroundColor
. In this way, we can omit it and leave its value implicit:
|
|
Notes:
We can use the default parameter also in methods with several parameters:
|
|
The Threat
For the sake of explanation, I changed APIRequestProtocol
, now it has just a method request
:
|
|
Once created our APIRequestProtocol
, and extended with the default implementation, we create a new class UsersAPIRequest
which conforms to APIRequestProtocol
:
|
|
This class doesn’t implement request
, but uses the default implementation.
Now, we can call the method request
:
|
|
request
uses the default parameter values for baseUrl
and entriesLimit
.
So far so good. Let’s introduce the threat:
Your boss introduces a new business logic. To achieve it, UsersAPIRequest
cannot use the default implementation of the protocol extensions anymore, therefore we add a custom request
implementation inside the class:
|
|
This code works and is fine.
But, if we call this new method, we’ll have an unexpected behaviour:
|
|
⚠️ We expect the compiler to call the method request
inside UsersAPIRequest
, instead, it calls the method inside the protocol extension.⚠️
This is the reason:
We have two methods request
in our hierarchy: Rpe
(Request of protocol extension) and Ruar
(Request of user api request).
When we write usersRequest.request(query: "?get=users")
, we ask the compiler to call Ruar
with just a parameter query
. It goes inside UsersAPIRequest
to read the implementation, but, unfortunately, it doesn’t find a method request
with just an explicit parameter query
, since Ruar
has 3 explicit parameter baseUrl
, query
, entriesLimit
:
func request(baseUrl: String, query: String, entriesLimit: Int?)
Usually, when Swift doesn’t find the right parameters of a method, it shows a compile error error: missing argument
. In this case, it doesn’t throw an error because we still have Rpe
, which has just an explicit query
parameter, and this is exactly what the compiler is looking for.
When we declare Ruar
, we don’t override the protocol extension implementation. To do it, Ruar
and Rpe
should have a default value in the same parameters—the values can be different, doesn’t matter.
Notes:
You may have noticed that I added the default parameter values in the extension instead of in the protocol. Swift doesn’t allow default values in the protocol declaration, but just in its extension—like in our example–or in the classes which conform to the protocol:
|
|
The Suggestions
We can remove this threat in different ways:
Adding the missing default parameters values also in UsersAPIRequest implementation
|
|
Adding the parameters explicitly when we call the method
|
|
Refactoring the method
We can create a new struct, which contains the parameters of the method, and move the default values inside it:
|
|
This kind of refactoring was introduced by Martin Fowler in his book Refactoring.
Conclusion
I agree, it may be a silly threat, and it occurs because of the developer’s distraction. Nevertheless, it can happen, and you would waste a lot of time understanding what’s going on. Sometimes, the issues because of distraction are the most difficult to solve.