
Swift Generics create elegant code, fit for a king or queen.
Update Note: This tutorial has been updated to Swift 3 by Gemma Barlow. The original tutorial was written by Mikael Konutgan.
Generic programming is a way to write functions and data types while making minimal assumptions about the type of data being used. Swift generics create code that does not get specific about underlying data types, allowing for elegant abstractions that produce cleaner code with fewer bugs – code fit for a king or queen. I certainly like my code being described as regal, don’t you?
You’ll find generics in use throughout Swift, which makes understanding them essential to a complete mastery of the language. An example of a generic you will have already encountered in Swift is the Optional type. You can have an optional of any data type you want, even those types you create yourself, of course. The Optional data type is made generic over the type of value it can contain.
In this tutorial, you’ll experiment in a Swift playground to learn:
- What exactly generics are
- Why they are useful
- How to write generic functions and data structures
- How to use type constraints
- How to extend generic types
Getting Started
Begin by creating a new playground. In Xcode, go to File\New\Playground…, name the playground Generics and select macOS as the platform. Click Next to choose where you’d like to save your new playground and finally, click Create.
As one of the few programmers residing in a kingdom far-far-away, you’ve been summoned to the royal castle to help the Queen with a matter of great importance. She has lost track of how many royal subjects she has and needs some assistance with her calculations.
She requests a function to be written that adds two integers. Add the following to your newly-created playground:
func addInts(x: Int, y: Int) -> Int { return x + y } |
addInts(x:y:)
takes two Int
values and returns their sum. You can give it a try by adding the following code to the playground:
let intSum = addInts(x: 1, y: 2) |
This is a simple example that demonstrates Swift’s type safety. You can call this function with two integers, but not any other type.
The Queen is pleased, and immediately requests another add
function be written – this time, adding Double
values. Create a second function addDoubles(x:y:)
:
func addDoubles(x: Double, y: Double) -> Double { return x + y } let doubleSum = addDoubles(x: 1.0, y: 2.0) |
The function signatures of addInts
and addDoubles
are different, but the function bodies are identical. Not only do you have two functions, but the code inside them is repeated. Generics can be used to reduce these two functions to one and remove the redundant code.
First however, you’ll look at a few other common occurrences of generic programming in everyday Swift.
Other Examples of Swift Generics
You may not have realized, but some of the most common structures you use, such as arrays, dictionaries and optionals, are generic types!
Arrays
Add the following to your playground:
let numbers = [1, 2, 3] let firstNumber = numbers[0] |
Here, you create a simple array of three numbers and then take the first number out of that array.
Now option-click, first on numbers
and then on firstNumber
. What do you see?
Because Swift has type inference, you don’t have to explicitly define the types of your constants, but they both have an exact type. numbers
is an [Int]
— that is, an array of integers — and firstNumber
is an Int
.
The Swift Array
type is a generic type. Generic types require a type parameter in order to be fully specified. When you create an instance, you specify a type parameter, which gives the instance a concrete type. Thanks to type inference and also thanks to Swift’s type safety, the array numbers
can only contain Int
values. When you remove anything from that array, Swift — and more importantly you — both know it must be an Int
.
You can better see the generic nature of Array
by adding a slightly longer version of the same code to the playground:
var numbersAgain: Array<Int> = [] numbersAgain.append(1) numbersAgain.append(2) numbersAgain.append(3) let firstNumberAgain = numbersAgain[0] |
Check the types of numbersAgain
and firstNumberAgain
by option-clicking on them; the types will be exactly the same as the previous values. Here you specify the type of numbersAgain
using explicit generic syntax, by putting Int
in angle brackets after Array
.
Try appending something else to the array, like a String
:
numbersAgain.append("All hail Lord Farquaad") |
You’ll get an error—something like, Cannot convert value of type ‘String’ to expected argument type ‘Int’
. The compiler is telling you that you can’t add a string to an array of integers. As a method on the generic type Array
, append
is a so-called generic method. It knows the type of the containing array’s elements, and won’t let you add something of an incorrect type.
Delete the line causing the error. Next you’ll look at another example of generics in the standard library.
Dictionaries
Dictionaries are also generic types and result in type-safe data structures.
Create the following dictionary of magical kingdoms at the end of your playground, and then look up the country code for Freedonia:
let countryCodes = ["Arendelle": "AR", "Genovia": "GN", "Freedonia": "FD"] let countryCode = countryCodes["Freedonia"] |
Check the types of both declarations. You’ll see that countryCodes
is a dictionary of String
keys and String
values—nothing else can ever be in this dictionary. The formal generic type is Dictionary
.
Optionals
In the example above, note the type of countryCode
is String?
, which is shorthand for
Optional<String> |
Do the < and > here – look familiar? Generics are all over the place!
Here the compiler enforces that you can only access the dictionary with string keys and you always get string values returned. An optional type is used to represent countryCode, because there might not be a value corresponding to the passed-in key. If we try to look up “The Emerald City”, for example, the value of countryCode would be nil
, as it doesn’t exist in your dictionary of magical kingdoms.
Add the following to your playground to see the full explicit syntax for creating an optional string:
let optionalName = Optional<String>.some("Princess Moana") if let name = optionalName {} |
Check the type of name
, which you’ll see is String
.
Optional binding, that is, the if-let
construct, is a generic transformation of sorts. It takes a generic value of type T?
and gives you a generic value of type T
. That means you can use if let
with any concrete type.
Now that you grasp of the basics of generics, you can learn about writing your own generic data structures and functions.
Writing a Generic Data Structure
A queue is a data structure kind of like a list or a stack, but one to which you can only add new values to the end (enqueue them) and only take values from the front (dequeue them). This concept might be familiar if you’ve ever used OperationQueue
– perhaps whilst making networking requests.
The Queen, happy with your efforts earlier in the tutorial, would now like you to write functionality to help keep track of royal subjects waiting in line to speak with her.
Add the following struct
declaration to the end of your playground:
struct Queue<Element> { } |
Queue
is a generic type with a type argument, Element
, in its generic argument clause. Another way to say this is, Queue
is generic over type Element
. For example, Queue<Int>
and Queue<String>
will become concrete types of their own at runtime, that can only enqueue and dequeue strings and integers, respectively.
Add the following property to the queue:
fileprivate var elements: [Element] = [] |
You’ll use this array to hold the elements, which you initialize as an empty array. Note that you can use Element
as if it’s a real type, even though it’ll be filled in later. You mark it as fileprivate
because you don’t want consumers of Queue
to access elements
. You want to force them to use methods to access the backing store. Also, using fileprivate
instead of private
will allow you to add an extension to Queue
later.
Finally, implement the two main queue methods:
mutating func enqueue(newElement: Element) { elements.append(newElement) } mutating func dequeue() -> Element? { guard !elements.isEmpty else { return nil } return elements.remove(at: 0) } |
Again, the type parameter Element
is available everywhere in the struct
body, including inside methods. Making a type generic is like making every one of its methods implicitly generic over the same type. You’ve implemented a type-safe generic data structure, just like the ones in the standard library.
Play around with your new data structure for a bit at the bottom of the playground, enqueuing waiting subjects by adding their royal id to the queue:
var q = Queue<Int>() q.enqueue(newElement: 4) q.enqueue(newElement: 2) q.dequeue() q.dequeue() q.dequeue() q.dequeue() |
Have some fun by intentionally making as many mistakes as you can to trigger the different error messages related to generics — for example, add a string to your queue. The more you know about these errors now, the easier it will be to recognize and deal with them in more complex projects.
Writing a Generic Function
The Queen has a lot of data to process, and the next piece of code she asks you to write will take a dictionary of keys and values, and convert it to a list.
Add the following function to the bottom of the playground:
func pairs<Key, Value>(from dictionary: [Key: Value]) -> [(Key, Value)] { return Array(dictionary) } |
Take a good look at the function declaration, parameter list and return type.
The function is generic over two types that you’ve named Key
and Value
. The only parameter is a dictionary with a key-value pair of type Key
and Value
. The return value is an array of tuples of the form—you guessed it — (Key, Value)
.
You can use pairs(from:)
on any valid dictionary and it will work, thanks to generics:
let somePairs = pairs(from: ["minimum": 199, "maximum": 299]) // result is [("maximum", 299), ("minimum", 199)] let morePairs = pairs(from: [1: "Swift", 2: "Generics", 3: "Rule"]) // result is [(2, "Generics"), (3, "Rule"), (1, "Swift")] |
Of course, since you can’t control the order in which the dictionary items go into the array, you may see an order of tuple values in your playground more like “Generics”, “Rule”, “Swift”, and indeed, they kind of do! :]
At runtime, each possible Key
and Value
will act as a separate function, filling in the concrete types in the function declaration and body. The first call to pairs(from:)
returns an array of (String, Int)
tuples. The second call uses a flipped order of types in the tuple and returns an array of (Int, String)
tuples.
You created a single function that can return different types with different calls. That is pretty rad. You can see how keeping your logic in one place can simplify your code. Instead of needing two different functions, you handled both calls with one function.
Now that you know the basics of creating and working with generic types and functions, it’s time to move on to some more advanced features. You’ve already seen how useful generics are to limit things by type, but you can add additional constraints as well as extend your generic types to make them even more useful.
Constraining a Generic Type
Wishing to analyze the ages of a small set of her most loyal subjects, the Queen requests a function to sort an array and find the middle value. Add the following function to your playground:
func mid<T>(array: [T]) -> T? { guard !array.isEmpty else { return nil } return array.sorted()[(array.count - 1) / 2] } |
You’ll get an error. The problem is that for sorted()
to work, the elements of the array need to be Comparable
. You need to somehow tell Swift that mid
can take any array as long as the element type implements Comparable
.
Change the function declaration to the following:
func mid<T: Comparable>(array: [T]) -> T? { guard !array.isEmpty else { return nil } return array.sorted()[(array.count - 1) / 2] } |
Here, you use the :
syntax to add a type constraint to the generic type parameter T
. You can now only call the function with an array of Comparable
elements, so that sorted()
will always work! Try out the constrained function by adding:
mid(array: [3, 5, 1, 2, 4]) // 3 |
Now that you know about type constraints, you can create a generic version of the add functions from the beginning of the playground – this will be much more elegant, and please the Queen greatly. Add the following protocol and extensions to your playground:
protocol Summable { static func +(lhs: Self, rhs: Self) -> Self } extension Int: Summable {} extension Double: Summable {} |
First, you create a Summable
protocol that says any type that conforms must have the addition operator +
available. Then, you specify that the Int
and Double
types conform to it.
Now using a generic parameter T
and a type constraint, you can create a generic function add
:
func add<T: Summable>(x: T, y: T) -> T { return x + y } |
You’ve reduced your two functions (actually more, since you would have needed more for other Summable
types) down to one and removed the redundant code. You can use the new function on both integers and doubles:
let addIntSum = add(x: 1, y: 2) // 3 let addDoubleSum = add(x: 1.0, y: 2.0) // 3 |
And you can also use it on other types, such as strings:
extension String: Summable {} let addString = add(x: "Generics", y: " are Awesome!!! :]") |
By adding other conforming types to Summable
, your add(x:y:)
function becomes more widely useful thanks to its generics-powered definition! Her Royal Highness awards you the kingdom’s highest honor for your efforts.
Extending a Generic Type
A Court Jester has been assisting the Queen by keeping watch over the waiting royal subjects, and letting the Queen know which subject is next, prior to officially greeting them. He peeks through the window of her sitting room to do so. We can model his behavior using an extension, applied to our generic Queue type from earlier in the tutorial.
Extend the Queue
type and add the following method right below the Queue
definition:
extension Queue { func peek() -> Element? { return elements.first } } |
peek
returns the first element without dequeuing it. Extending a generic type is easy! The generic type parameter is visible just as in the original definition’s body. You can use your extension to peek into a queue:
q.enqueue(newElement: 5) q.enqueue(newElement: 3) q.peek() // 5 |
You’ll see the value 5
as the first element in the queue, but nothing has been dequeued and the queue has the same number of elements as before.
Royal Challenge: Extend the Queue
type to implement a function isHomogeneous
that checks if all elements of the queue are equal. You’ll need to add a type constraint in the Queue
declaration to ensure its elements can be checked for equality to each other.
Subclassing a Generic Type
Swift has the ability to subclass generic classes, which can be useful in some cases, such as to create a concrete subclass of a generic class.
Add the following generic class to the playground:
class Box<T> { // Just a plain old box. } |
Here you define a Box
class. The box can contain anything, and that’s why it’s a generic class. There are two ways you could subclass Box
:
- You might want to extend what the box does and how it works but keep it generic, so you can still put anything in the box;
- You might want to have a specialized subclass that always knows what’s in it.
Swift allows both. Add this to your playground:
class Gift<T>: Box<T> { // By default, a gift box is wrapped with plain white paper func wrap() { print("Wrap with plain white paper.") } } class Rose { // Flower of choice for fairytale dramas } class ValentinesBox: Gift<Rose> { // A rose for your valentine } class Shoe { // Just regular footwear } class GlassSlipper: Shoe { // A single shoe, destined for a princess } class ShoeBox: Box<Shoe> { // A box that can contain shoes } |
You define two Box
subclasses here: Gift
and ShoeBox
. Gift
is a special kind of box, separated so that we may have different methods and properties defined on it, such as wrap()
. However, it still has a generic on the type, meaning it could contain anything. Shoe
and GlassSlipper
, a very special type of shoe, have been declared, and can be placed within an instance of ShoeBox
for delivery (or presentation to an appropriate suitor).
Declare instances of each class under the subclass declarations:
let box = Box<Rose>() // A regular box that can contain a rose let gift = Gift<Rose>() // A gift box that can contain a rose let shoeBox = ShoeBox() |
Notice that the ShoeBox
initializer doesn’t need to take the generic type parameter anymore, since it’s fixed in the declaration of ShoeBox
.
Next, declare a new instance of the subclass ValentinesBox
– a box containing a rose, a magical gift specifically for Valentine’s Day.
let valentines = ValentinesBox() |
While a standard box is wrapped with white paper, you’d like your holiday gift to be a little fancier. Add the following method to ValentinesBox
:
override func wrap() { print("Wrap with ♥♥♥ paper.") } |
Finally, compare the results of wrapping both of these types by adding the following code to your playground:
gift.wrap() // plain white paper valentines.wrap() // ♥♥♥ paper |
ValentinesBox
, though constructed using generics, operates as a standard subclass with methods that may inherited and overridden from a superclass. How elegant.
Enumerations With Associated Values
A common “functional” error-handling idiom is to use a so-called result enum
. This is a generic enum
with two associated values: one for the actual result value, and one for the possible error.
This will allow you to write elegant error handling for a division method requested by the Queen – her final ask of you.
Add the following declaration to the end of your playground:
enum Result<Value> { case success(Value), failure(Error) } |
The main use case for such an enum
is as a return value for a function with specific error information using the standard library Error
type, kind of like a more general optional. Add the following to the end of your playground:
enum MathError: Error { case divisionByZero } func divide(_ x: Int, by y: Int) -> Result<Int> { guard y != 0 else { return .failure(MathError.divisionByZero) } return .success(x / y) } |
Here, you declare an error enumeration type and a function that divides two integers. If the division is legal, you return the resulting value in the .success
case; otherwise you return a MathError
.
Add the function above to your playground, and then try it out by adding the following:
let result1 = divide(42, by: 2) // .success(21) let result2 = divide(42, by: 0) // .failure(MathError.divisionByZero) |
The first result is a success case with a value of 21, and the second result is the failure case with the enumeration case .divisionByZero
. Even though the enumeration has two generic parameters associated with it, you can see how the cases are defined to use just one or the other.
Where to Go From Here?
Here’s the downloadable final playground with all the code you’ve written in this tutorial.
Swift generics are at the core of many common language features, such as arrays and optionals. You’ve seen how to use them to build elegant, reusable code that will result in fewer bugs – code fit for royalty.
For more information, read through the Generics chapter and the Generic Parameters and Arguments language reference chapter of Apple’s guide, The Swift Programming Language. You’ll find more detailed information about generics in Swift, as well as some handy examples.
If you’re feeling especially excited, you may also like to read up on likely changes for generics in Swift 4 – Planned Future Features.
A good next topic, to build upon what you’ve learned in this tutorial, is Protocol Oriented Programming – see Introducing Protocol Oriented Programming by Niv Yahel for more details.
Generics in Swift are an integral feature that you’ll use everyday to write powerful and type-safe abstractions. Improve your commonly-used code by remembering to ask “Can I genericize this?”
If you have any questions, I’d love to hear from you in the forum discussion below! :]
The post Swift Generics Tutorial: Getting Started appeared first on Ray Wenderlich.