Generics in Swift

Thibault Carpentier
5 min readJan 15, 2018

Generics is one of the most powerful features introduced by Swift. Generics Types in the code allow to write flexible, reusable code while still maintaining a strong type check at compilation time. Generics are mostly used to avoid code duplication and provide a high level of abstraction.

The problem

To understand generics, nothing beats an example put into context. Let’s take one of the most common uses of generics in swift: Collections. Let’s define a list of Any element.

Here we have a list with disparate elements and we can add anything as an element to this list. Swift actually infers the generic Any type (with most recent versions of swift, the compiler forces you to explicitly type the variable type). This implies that we need to manually type the objects in this list to use them:

Now, let’s imagine that we want a list that contains only Integers and be sure that there are only Integers. We could check this with code when we add the elements to the list but this will not prevent any errors. This is where generics intervene. We will “say” to the list that it can only contain integers.

We specify that the type of object that can contain our integerList are Int. This makes it very easy to browse a list without worrying about the type it contains:

Now let’s try adding invalid elements:

The main advantage of generics as you can see is its ability to check types constraints at compilation time and thus avoid any errors when running the program.

Now, let’s take a case where the use of AnyObject or Any would cause problems. Let’s use for example this method to swap two Strings.

Let’s try and replace the String types with the Any type:

While everything seems to compile and work here, in reality if we use two different types in the parameters (for example a String and an Int ) the program will crash at runtime. Since the compiler doesn’t know that it needs to enforce that the parameters have to be of the same type, this will compile normaly but crash when an incorrect use occurs.

Genericity

To make the method of our previous example generic and solve this problem, we will use templates:

The main difference at first glance is the presence of chevron <> and the typeT. First of all, the type T is arbitrary. We could have used X , Y or any other strings that does not correspond to an existing type. The chevrons are there to say that it is a templated function using the type T. Since the type T is not defined as an existing type, we can use any type of variable. However, both a and b are of type T. This means that we managed here to put a constraint on the types so that a and b can not be different. And this is enforced during the compilation:

We can also define functions that use more than one generic type. For example:

public func example<T, U, V>(p1:T, p2:U, p3:V) {}

Being able to use very generic types such as Any or AnyObject can sometimes be an advantage, but it is often a red flag signaling the need to use generics to protect type checks during compilation time.

Type constraints

Even if Templates help with a large amount of use cases to protect some of the constraints your method may have, this may not be enough for some use cases. You can on top of the templates apply some more constraints. The first type of constraints is the specification of the inheritance or compliance of a protocol on a generic type.

Let’s imagine that we have an application that sells different products. In this store we have books, movies and music that we can define in a Media class that inherits from Product. Now we can define a generic function that only sorts Media according to different criteria such as the styleNumber.

Here is a corresponding example method signature:

This generic function takes as parameter a list of type T accepting only types inheriting from Media:

The constraint on the inheritance makes it possible to ensure the type of objects which will be passed as parameter.

The second type of constraints that can be applied on templates is with the where clause. This clause allows you to define several constraints at once. Let's take the example of the sort of media, add as a constraint that we only want AwesomeProduct media. For that we have a protocol AwesomeProduct which guarantees that:

func sortAwesomeByStyle<T>(collection: inout [T]) -> [T] where T:Media, T:AwesomeProduct { [...]}

Check out the official documentation to see all the possibilities offered by the where clause.

Generic types

Genericity does not only apply to methods or function (at least in Swift), but also to class , struct and enum . This allows you to create generic types or objects.

For example, Array and Dictionary are generic types.

Let’s define an object that contains an integer list and allows us to draw one of these integers randomly:

But now, what happens if instead of an integer we wanted to use String ? Without genericity we would have to create a new RandomPoolString object that would have exactly the same structure and implementation. But thanks to generics we will be able to write much more elegant code:

Now you can create a bag of String or Int or anything else without rewriting the same code every-time.

--

--