Working with Generics in Swift

As you may already know, Swift has support for generics, and if you’re going to use Swift effectively, you need to be able to read generics at the very least. And if you want to be really good at Swift, you should be able to write code with generics.

(You should play along at home by copying the code below into a new Swift Playground.)

Let’s start by making a function that prints each character in a String. How does this look?

func printEachCharacter(str: String)
{
    for character in str
    {
        println(character)
    }
}

Now we should see each character printed to the console…

let greeting = "Hello"
printEachCharacter(greeting)

But what if we want to print each element in an array? How do we do that? We might try this:

func printEachElementInArray(arr: [Any])
{
    for element in arr
    {
        println(element)
    }
}

But does that work?

let people = ["Sally", "Jim"]
printEachElementInArray(people)

Nope. The compiler doesn’t like printEachElementInArray(people). You should see the following error on that line:

Execution was interrupted, reason: EXC_BAD_INSTRUCTION…

Clearly, the compiler isn’t being very helpful here, but the issue is with the type of the array we’re passing in – it’s an array of String, and the function is expecting an array of Any. Why can’t the compiler figure this out? I’m not sure, but…

We could change the people to look like this:

let people: [Any] = ["Sally", "Jim"]

And that works just fine. But now you no longer have an array of Strings – you have an array of Any. And you can’t do String things on the elements without casting. For example, you can’t do this:

people[0].isEmpty

Since that’s a String method – we’d have to cast people[0] to a String. But that sorta defeats the purpose of having typed arrays, doesn’t it?

(This might be a good time to delete or comment out the broken code above.)

So what can we do?

Could generics solve this problem for us? Let’s give it a shot.

func printEachElement(arr: [T])
{
    for element in arr
    {
        println(element)
    }
}

How does that look?

let friends = ["Sally", "Jim"]
printEachElement(friends)

But more importantly, how does it work? What’s the T, and what are those angle brackets?

The <T> after the method name indicates that this is a generic function. For its only parameter, it takes an array whose objects are of type T – and T can be any type. So it takes an array containing any type.

Now, this printEachElement function won’t work if we try to pass in a String, since it’s expecting an array. So what if we want a function that prints each element of either a String or an Array? Let’s try this:

func printEach(things: T)
{
    for thing in things
    {
        println(thing)
    }
}

And then run it:

let name = "Josh"
printEach(name)

let veggies = ["cucumber", "carrot"]
printEach(veggies)

(Yep, it works.)

But does the code make sense?

So again here, we have a generic function printEach that takes a parameter that’s of type T. This time, T must be a SequenceType (something we can iterate over), as indicated by the <T: SequenceType> just after the method name. And as you can see, we can use the for...in loop to iterate over each element in a SequenceType.

Using generics in Swift can be very powerful – much more powerful than this little example. In fact, the Swift standard library uses generics heavily, giving you, the developer, the ability to call functions with a variety of types. The sorted function, for example, uses generics, so at the very least, you need to be able to read generic code. And now you can. :)

The Playground for all of this is on GitHub.

Learn to build crash-free apps in Swift by learning how to handle type casting, optionals, parsing JSON, and more in the 5-Part Guide to Getting Started to Swift by dropping your name and email in the boxes below.