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.
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.
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
query. Then, we create two classes,
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
In this way, if the compiler doesn’t find an
APIRequestProtocol implementation inside
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:
We can use the default parameter also in methods with several parameters:
For the sake of explanation, I changed
APIRequestProtocol, now it has just a method
Once created our
APIRequestProtocol, and extended with the default implementation, we create a new class
UsersAPIRequest which conforms to
This class doesn’t implement
request, but uses the default implementation.
Now, we can call the method
request uses the default parameter values for
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
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
Ruar has 3 explicit parameter
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,
Rpe should have a default value in the same parameters—the values can be different, doesn’t matter.
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:
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.
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.