LEARN SWIFT

Buy the PDF

Chapter 19 Generics

Up to now, when we’ve defined a function we’ve always provided a specific type for its arguments and return type. Generics are a way of defining functions or types that can work with multiple different data types.

Generics are used heavily throughout Swift itself. For example the Array is defined as a generic type.

19.1 Generic Functions

You define a generic function by using angle brackets with a type placeholder after the function name. The convention is for this placeholder to be called T.

func doNothing<T> (x:T) -> T {
    return x
    }

doNothing(1) // 1

doNothing("hello") // "hello"

In this example we define a function that does nothing except return the parameter that is passed to it. By adding <T> after the function name we’ve indicated that this is a generic function and that used T as a placeholder for the type. In the functions type signature we have indicated that it takes a parameter called x of type T and returns something of type T.

We can call this function with an integer or a string and it works with either one.

19.2 Generic Types

Arrays in swift are generic – you can use them with any type. You can also define classes, structs or enumerations that will work with any type.

struct LifoQueue<T> {
    var items = [T]()

    mutating func enqueue(element:T){
        items.append(element)
    }

    mutating func dequeue() -> T{
        return items.removeAtIndex(0)
    }
}

var q = LifoQueue<Int>() // {0 elements}

q.enqueue(2) // {[2]}
q.enqueue(3) // {[2, 3]}
q.enqueue(4) // {[2, 3, 4]}

q.dequeue() // 2
q.dequeue() // 3
q.dequeue() // 4

var q2 = LifoQueue<String>() // {0 elements}
q2.enqueue("Hello") // {["Hello"]}
q2.enqueue("I")     // {["Hello", "I"]}
q2.dequeue()        // "Hello"

In this example we define a generic data structure that implements a Last-In-First-Out queue. Similar to our generic function we indicate that the structure is generic by adding <T> after the name of the struct and then we can use T to refer to the type in the structs definitions. So for this example we declare an array of type T at line 3, an enqueue function that takes an element of type T at line 5 and a function that returns an element of type T at line 9.

19.3 Type constraints

We can constrain the types that are allowed to be used in a generic either by class or by protocol.

Lets say we only wanted our Queue to accept numeric types. We could define a numeric protocol and say that Int, Float, and Double conform to it. Then we can define a generic queue that only accepts types that conform to this protocol.

protocol Numeric { }

extension Float: Numeric {}
extension Double: Numeric {}
extension Int: Numeric {}

struct ConstrainedLifoQueue<T: Numeric> {
    var items = [T]()

    mutating func enqueue(element:T){
        items.append(element)
    }

    mutating func dequeue() -> T{
        return items.removeAtIndex(0)
    }
}


var q3 = ConstrainedLifoQueue<Float>()  // Ok
var q4 = ConstrainedLifoQueue<String>() // Error