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