DEV Community

Fernando Martín Ortiz
Fernando Martín Ortiz

Posted on • Edited on

Swift generics

Note: This article is part of the course Introduction to iOS using UIKit I've given many times in the past. The course was originally in Spanish, and I decided to release it in English so more people can read it and hopefully it will help them.

The problem

Let's imagine we need to write a Stack data structure. We could do something like this:

struct Stack {
    private var content: [Int] = []

    var topElement: Int? {
        return content.last
    }

    mutating func push(_ element: Int) {
        content.append(element)
    }

    mutating func pop() -> Int? {
        return content.popLast()
    }
}
Enter fullscreen mode Exit fullscreen mode

And we can use it like this:

var stack = Stack()
stack.push(10)
stack.push(20)
print(stack.topElement) // 20
stack.push(15)
let topElement = stack.pop()
print()
Enter fullscreen mode Exit fullscreen mode

The problem we'll have with this type is that if we need to define a StringStack, then we'd have to repeat the entire code!

struct StringStack {
    private var content: [String] = []

    var topElement: String? {
        return content.last
    }

    mutating func push(_ element: String) {
        content.append(element)
    }

    mutating func pop() -> String? {
        return content.popLast()
    }
}
Enter fullscreen mode Exit fullscreen mode

The solution

The solution to that problem is generics, and it consists on replacing the specific types that will vary from implementation to implementation, by a template or generic type.

struct Stack<T> {
    private var content: [T] = []

    var topElement: T? {
        return content.last
    }

    mutating func push(_ element: T) {
        content.append(element)
    }

    mutating func pop() -> T? {
        return content.popLast()
    }
}
Enter fullscreen mode Exit fullscreen mode

We don't have a IntStack and a StringStack as separate types anymore. What we have now is a Stack of type T, and that T can be replaced by a concrete type when we need a Stack.

var stack = Stack<Int>()
stack.push(10)
stack.push(20)
print(stack.topElement) // 20
stack.push(15)
let topElement = stack.pop()
print()
Enter fullscreen mode Exit fullscreen mode

Constrained generics

Sometimes we need to force generic types to implement certain requisites. The most common case is that we need the generic type to implement a protocol.

protocol Noisy {
    func makeNoise() 
}

struct RingBell: Noisy {
    func makeNoise() {
        print("Ring!")
    }
}

struct Dog: Noisy {
    func makeNoise() {
        print("Woof!")
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's now imagine we want to create a noisy stack, so we can then have all our noisy objects to make noise at the same time!

struct NoisyStack<T: Noisy> {
    private var content: [T] = []

    var topElement: T? {
        return content.last
    }

    mutating func push(_ element: T) {
        content.append(element)
    }

    mutating func pop() -> T? {
        return content.popLast()
    }

    func makeNoise() {
        for item in content {
            item.makeNoise()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this case, as the items in the NoisyStack are Noisy, we know that all the items on can make noise.

Examples

There are other examples of generic types we have been using so far.

The first one is Array. When we write [Int], we're actually creating a Array<Int>, but with a simplified syntax.

The second example is probably more interesting. Int? is actually Optional<Int>, but with some syntax sugar on it, but it's exactly the same as writing something like this:

enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)