Swift: Generics

Objective-C lacks one very important feature: Generics. Luckily, Swift has this feature. Generics enable you to declare functions, classes and structs that work with different types.

The Problem

The most common example for a good generics use case is a stack. A stack is a container that has two operations: “Push” for adding an item to the container and “Pull” to remove the last item from the container. First we are programming the stack without generics. The result looks like this:

class IntStack {
    
    private var stackItems: [Int] = []
    
    func pushItem(item:Int) {
        stackItems.append(item)
    }
    
    func popItem() -> Int? {
        guard let lastItem = stackItems.last else {
             return nil
        }
        stackItems.removeLast()
        return lastItem
    }
    
}

This stack is able to handle integers. So what can we do if we want to build a stack that can handle strings? Then we need to replace “Int” at all places and that’s for sure a very bad solution. Another solution that is looking good at the first sight is the following:

class AnyObjectStack {
    private var stackItems: [AnyObject] = []
    
    func pushItem(item:AnyObject) {
        stackItems.append(item)
    }
    
    func popItem() -> AnyObject? {
        guard let lastItem = stackItems.last else {
             return nil
        }
        stackItems.removeLast()
        return lastItem
    }
}

We just use the type AnyObject, so now we are able to push strings to the stack, right? However, in this case we are losing type safety and when we are using the stack we also need to do a lot of casting.

The Solution

With generics you are able to define a generic type, that behaves like a placeholder. Our example with a generic type:

class Stack<T> {
    
    private var stackItems: [T] = []
    
    func pushItem(item:T) {
        stackItems.append(item)
    }
    
    func popItem() -> T? {
        guard let lastItem = stackItems.last else {
             return nil
        }
        stackItems.removeLast()
        return lastItem
    }
    
}

A generic is defined with the diamond operator, in this case we call it  T . At initialisation time we define the parameter and in the following all T ‘s are replaced by the compiler with that type:

let aStack = Stack<Int>()

aStack.pushItem(10)
if let lastItem = aStack.popItem() {
    print("last item: \(lastItem)")
}

The big advantage is, that we can use now the stack with any type.

Type Constraints

There is one disadvantage: Since a generic can be of any type, we can’t do a lot with it. So even comparing two generics won’t work:

class Stack<T> {
    
    private var stackItems: [T] = []
    
    func pushItem(item:T) {
        stackItems.append(item)
    }
    
    func popItem() -> T? {
        guard let lastItem = stackItems.last else {
             return nil
        }
        stackItems.removeLast()
        return lastItem
    }
    
    func isItemInStack(item:T) -> Bool {
        var found = false
        for stackItem in stackItems {
            if stackItem == item { //Compiler Error!
                found = true
            }
        }
        return found
    }
}

In the function isItemInStack(item:T)  we get a compiler error, because two values can only be compared if their corresponding type implements the Equatable protocol. But it is actually possible to define constraints for the type of the generic. In this can we want to require that the generic implements the Equatable protocol by changing the first line:

class Stack<T: Equatable> {
    
    private var stackItems: [T] = []
    
    func pushItem(item:T) {
        stackItems.append(item)
    }
    
    func popItem() -> T? {
        guard let lastItem = stackItems.last else {
             return nil
        }
        stackItems.removeLast()
        return lastItem
    }
    
    func isItemInStack(item:T) -> Bool {
        var found = false
        for stackItem in stackItems {
            if stackItem == item {
                found = true
            }
        }
        return found
    }
}

[thrive_text_block color=”blue” headline=”Conclusion”]Like in many other programming language, you can take advantage of this feature also in Swift. Generics are very handy if you want to write for example a library.[/thrive_text_block]

References

Image: @ Marynchenko Oleksandr / shutterstock.com
Objective-C: Lightweight Generics

2 comments

  1. I would suggest the following for pop just in case you try to pop an element from an empty stack :

    func popItem() -> T? {
    guard let lastItem = stackItems.last else {
    return nil
    }
    stackItems.removeLast()
    return lastItem
    }

    and remember to remove the comment ” //CompilerError!!!!” from the code snippet

Comments are closed.