As a developer I really enjoy writing clear, readable and reusable code. Lately I have been learning more about Swift’s generics and how it can make my code more flexible and reusable. In this post I will explain to you the concept of generics and give you some examples of how I use this nice feature of Swift in my projects.
What are generics
Generics allow us to create types and functions that can work with any type. Generic code is flexible and reusable and makes it easier for us to avoid code duplication. Generics are a very important feature of Swift. Even if you’re new to Swift and have never created a generic type or function, you have used generics. This is because a big part of the Swift standard library is written with the help of generics, for example Swift’s array. Because in Swift an array can hold any type, whenever we initialize a new array we always have to specify which type it will hold.
Generic types are custom classes, structs or enums that can work with any type, in the same way as Swift’s Array and Dictionary allow us to do that.
Let say we are developing a music app which displays a list of popular songs, popular artists and upcoming concerts which we fetch from a backend from a popular music site. These lists can be quite long so we get a paginated response from the backend. A request for a list looks something like this:
[“page”: 1, “totalPages”: 20, “totalItems”: 400, “items”: [Aryay of dictionaries] ]
Okay so now we have to think about how we want to create our model objects. Besides creating objects for a song, an artist and a concert we also need an object that represents a list. This could be a list of songs, artist, concerts or if later in the development process we decide that we also want to display a list of albums we should be able to use the same List object. Thats why this object will be a generic type. This will look something like this:
In this example Item is the generic parameter. Every time you initialize a generic object you need to specify which actual type to use in place of the generic parameter, just like you would do with an array or a dictionary. You can provide more than one generic parameter by writing multiple type parameter names within angle brackets, separated by commas. It is a best practice to give these parameters a descriptive name to tell readers of our code about the relationship between the generic parameter and the generic type. When this relationship does not exist you should name them using single letters such as T, U or V. Another best practice is to always use upper case names to indicate that they are a placeholder for a type and not a value.
Right we could pas any type of object inside of our list model and it works. Thats nice, however what if we want to enforce constraints on the types that can be used with our generic List object. In our music app we for example might want to cache the data on the disk with NSKeyedArchiver. For this we normally would use NSCoding to encode and decode our object but unfortunately NSCoding does not work with Swift structs. To solve this problem we create the following protocol which all our model objects that we want to save to disk need to conform to:
Now we can add a type constraint to the generic parameter of our List object. Type constraints specify that a generic parameter must inherit from a specific class, or conform to a particular protocol or protocol composition. Our List object now looks something like this:
A generic function is a function that can work with any type. Just as with generic types a generic function has a generic parameter or placeholder and the actual type to use in place of this parameter will be determined each time the function gets called.
You can make protocols generic by declaring one or more associated types as part of the protocol’s definition. An associated type gives a placeholder name to a type that is used as part of the protocol. Whenever the protocol is adopted you declare which actual type(s) to use for the associated type(s) in that protocol. In other words the protocol does not know the exact type so every class, struct, enum that adopts that protocol should fill in the details. So working with generics in protocols does not seem very different from working with generics in classes and structs. However there is one important difference. Instead of that the type gets specified when we instantiate a class or a struct with associated types it gets specified when we adopt a protocol, which hides this for the outside world. I do not have a lot of experience with associated types but I did use it in a project which was heavy on table views and collection views.For this I created a ‘dataprovider’ protocol which contains basic properties and functions that I want in my UITableViewDataSource and UICollectionViewDataSource objects and gave it a default implementation. This looked something like this:
That’s it. I hope you learned enough about generics so you can start using this powerful feature of Swift in your own projects.