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()
}
}
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()
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()
}
}
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()
}
}
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()
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!")
}
}
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()
}
}
}
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)
}
Top comments (0)