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)