Generic type extension by retrofitting protocols in Swift 2

Rationale

A common Swift problem is that extensions of generics can only be constrained to protocols. We use a lot of big words here but this issue is quite common and a simple example will clearly illustrate this situation.

So, let’s assume that we want to detect if an optional string is empty or nil.

The basic solution would look something like this:

This snippet looks a bit bloated for such a simple operation and we would rather use something like this:

However, the isEmpty property does not exist in Optional type and we immediately think of adding an extension to Optional type to help with this :

But there we fall short on a Swift limitation :

Type ‘Wrapped’ constrained to non-protocol type ‘String’.

Unfortunately, swift only accepts protocol as constraints on generic type extensions :-(
Constraining generic protocols, especially, do not work on value types such as: structs, enums and base types.

An interesting thing is, that swift implicitly provides a protocol for classes describing their interface. You can therefore constraint to classes easily.

Now, lets see how to make constraints on value types.

Solution

Let’s retrofit it on String: we only need to declare it, because the implementation already exists :

Now let’s update our Optional extension to support the protocol we introduced:

and, thats it!

Bonus code: we can even retrofit more implementations as long as they conform to this signature !

Thank you for reading, I hope you enjoyed this post.

If you have any feedback, improvement or just want to say hello, here is my twitter @kamidude

Addendum

I’ve received an interesting feedback that I would like to include in this post:

  • Extending Optional (especially in libraries) may not be a good idea. Polluting base types and global namespace is not the best practice. However, this article intends to be an illustration of retrofitting / extending types with constraints.

Full code gist

https://gist.github.com/jeremiegirault/a2f41f98f9a41cb88f39