Generics in Swift

      No Comments on Generics in Swift

Generics is one powerful feature of swift that allows us to work with generic types. In this article, we’ll cover what generics are, and how to use them in your project.

Generics

You must have come across the term ‘generics’ when working in swift. They’re those funky things in angle brackets. What generics give us is the ability to use a generic type. You probably have a couple of methods that are almost identical, the only difference is in the parameter type and the return type. Old school programmers will remember function overloading. Well, with generics, you can write one function that you don’t need to overload, and that accepts generic types and returns a generic type.

We get one obvious benefit from using generics, we reduce code duplication. And we all know, less code equals fewer bugs (yes, no code equals no bugs, and no job too 🙂 ). Let’s go over different kinds of generics, and how to use them.

Functions

We can use generics in our function signatures. Let’s say we want to make a function that will work with a generic type. The function will accept any type as it’s parameter and do something useful with it. One common example would be storing an item in an array, we don’t care what’s the type of the item, we just store it in the array. You can see an example of this method below:

func genericFunction<Item>(item: Item, secondItem: Item) {
    // do something useful with these two items :)
}

// Integers as parameters
genericFunction(item: 2, secondItem: 3)

// Strings as parameters
genericFunction(item: "first", secondItem: "second")

Let’s go over this example. The first thing you’ll notice is the function signature. There’s an angle bracket between the function name and the function parameters. Within the angle brackets, we have a word ‘Item’. This is simply a label that we use, ‘Item’ is not a built-in type or an existing class. It’s simply a type label. We can read this as ‘something of type Item’. The next place where we’re using this imaginary type is in the function parameters. We’re saying that we’ll accept two parameters, and both of them have to be of type ‘Item’. So both of them have to be the same.

What we have now is a generic function that accepts two parameters of any type, as long as both of them are of the same type. We can see this demonstrated in the following lines of code. In the first example we’re passing two integers and in the second we’re passing two strings. Pretty cool 🙂

Type Parameters

In the example above we used generics in a function. We used a label ‘Item’. This label is called a ‘type parameter’. It basically means that the type is used as a variable parameter when you call your function.

We can also use type parameter in our class declaration. This way the type parameter is scoped on the whole class, as opposed to the method scope in the example above. An example of a class type parameter looks something like this:

class Node<T> {
    var item: T
    var next: Node?
    
    init(withItem item: T) {
        self.item = item
    }
}

In this example ‘T’ is a type parameter. We simply add it right after the class name in angle brackets. This will give us a type ‘T’ that we can access anywhere from within our class. For example, we can declare variables of type ‘T’ and treat ‘T’ as a built-in type. Our class ‘Node’ can be constructed with any type and it will hold a reference to it, along with a reference to the next node (this code is from one of my previous posts about linked lists).

And as you might expect, you have access to your type parameters in the class extensions, like so:

extension Node {
    func processItem(item: T) {
        // Access type parameter in the extension
    }
}

Type Constraints

Type parameters are nice, but the real power comes from type constraints. With a type constraint, you can restrain your type parameter. For example, you might want your type parameter to be a class that’s subclassing a certain class, or even better a protocol. When you do something like that, you will get access to the protocol/base class methods. Let’s examine one example:

class DASelectionSort<T: Comparable> {
    
    public static func sort(_ items: [T]) -> [T] {
        var result = items
        
        let length = result.count
        
        for i in 0..<length {
            var minIndex = i
            for j in i+1..<length {
                if result[j] < result[minIndex] {
                    minIndex = j
                }
            }
            result.swapItems(itemAtIndex: i, withItemAtIndex: minIndex)
        }
        
        return result
    }
}

In this example, we’re saying that our type parameter must implement the ‘Comparable’ protocol. Because of this constraint, we can now compare two elements of type ‘T’ and use this generic sort method to sort any array of objects as long as they implement the comparable protocol. Let’s take our ‘Node’ example a bit further:

protocol MyCustomProtocol {
    var itemTitle: String { get }
}

class Node<T: MyCustomProtocol> {
    var item: T
    var next: Node?
    
    init(withItem item: T) {
        self.item = item
    }
}

extension Node {
    func printItemTitle(item: T) -> String {
        return item.itemTitle
    }
}

Here is the real power of generics. We are using a type parameter ‘T’ that has to implement a certain protocol (MyCustomProtocol in our example), and we can use it like any other object.

We can also constrain a type even further using a where clause, for example:

extension Node where T: Equatable {
    func printItemTitle(item: T, secondItem: T) -> String {
        if item == secondItem {
            return "equal"
        } else {
            return item.itemTitle
        }
    }
}

In this example, we’re saying that our generic type ‘T’ has to be equatable in order to use this function. We’ll be comparing two elements in the function. You can also construct pretty complex where clauses, depending on your use case.

Associated Types

You can’t define a type parameter on a protocol, if you try, you’ll get an error like this one:

The solution to the problem is suggested to you by Xcode. We can use associated types instead of a generic type parameter. Using this is very similar to a type parameter, we just need to provide a type when we implement the protocol. Let’s see a small example:

protocol MyOtherProtocol {
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

class Testing: MyOtherProtocol {
    
    typealias ItemType = Int
    
    internal func append(_ item: Int) {
        //
    }

    internal var count: Int = 0

    internal subscript(i: Int) -> Int {
        return 2 // Just for test
    }
}

In the protocol definition, we create an associatedType, in this case, ‘ItemType’. And when we implement the protocol we simply assign the actual type to it with a typealias. Now when we implement the functions of the protocol we’ll be using the actual type, instead of the generic one.

Conclusion

Generics are a really powerful feature on swift. And if you use them properly you will reduce code duplication and make your code a lot more readable. They’re an integral part of swift language, and if you look closely you’ll find them everywhere (e.g. optionals). I hope this post opened some doors for you and that you’re thinking about how to use generics in your own projects.

Have a nice day 🙂

Dejan.

More resources

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.