Swift Generics Tutorial: Getting Started
Learn to write functions and data types while making minimal assumptions. Swift generics allow for cleaner code with fewer bugs. By Michael Katz.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Swift Generics Tutorial: Getting Started
25 mins
- Getting Started
- Other Examples of Swift Generics
- Arrays
- Dictionaries
- Optionals
- Results
- Writing a Generic Data Structure
- Writing a Generic Function
- Constraining a Generic Type
- Cleaning Up the Add Functions
- Extending a Generic Type
- Subclassing a Generic Type
- Enumerations With Associated Values
- Where to Go From Here?
Cleaning Up the Add Functions
Now that you know about type constraints, you can create a generic version of the add
functions from the beginning of the playground — this will be much more elegant, and please the Queen greatly. Add the following protocol and extensions to your playground:
protocol Summable { static func +(lhs: Self, rhs: Self) -> Self }
extension Int: Summable {}
extension Double: Summable {}
First, you create a Summable
protocol that says any type that conforms must have the addition operator +
available. Then, you specify that the Int
and Double
types conform to it.
Now using a generic parameter T
and a type constraint, you can create a generic function add
:
func add<T: Summable>(x: T, y: T) -> T {
return x + y
}
You’ve reduced your two functions (actually more, since you would have needed more for other Summable
types) down to one and removed the redundant code. You can use the new function on both integers and doubles:
let addIntSum = add(x: 1, y: 2) // 3
let addDoubleSum = add(x: 1.0, y: 2.0) // 3.0
And you can also use it on other types, such as strings:
extension String: Summable {}
let addString = add(x: "Generics", y: " are Awesome!!! :]")
By adding other conforming types to Summable
, your add(x:y:)
function becomes more widely useful thanks to its generics-powered definition! Her Royal Highness awards you the kingdom’s highest honor for your efforts.
Extending a Generic Type
A Court Jester has been assisting the Queen by keeping watch over the waiting royal subjects, and letting the Queen know which subject is next, prior to officially greeting them. He peeks through the window of her sitting room to do so. You can model his behavior using an extension, applied to our generic Queue
type from earlier in the tutorial.
Extend the Queue
type and add the following method right below the Queue
definition:
extension Queue {
func peek() -> Element? {
return elements.first
}
}
peek
returns the first element without dequeuing it. Extending a generic type is easy! The generic type parameter is visible just as in the original definition’s body. You can use your extension to peek into a queue:
q.enqueue(newElement: 5)
q.enqueue(newElement: 3)
q.peek() // 5
You’ll see the value 5 as the first element in the queue, but nothing has been dequeued and the queue has the same number of elements as before.
Royal Challenge: Extend the Queue
type to implement a function isHomogeneous
that checks if all elements of the queue are equal. You’ll need to add a type constraint in the Queue
declaration to ensure its elements can be checked for equality to each other.
[spoiler title=”Homogeneous queue”]
Answer:
First edit the definition of Queue
so that Element
conforms to the Equatable
protocol:
struct Queue<Element: Equatable> {
Then compose isHomogeneous()
at the bottom of your playground:
extension Queue {
func isHomogeneous() -> Bool {
guard let first = elements.first else { return true }
return !elements.contains { $0 != first }
}
}
Finally, test it out:
var h = Queue<Int>()
h.enqueue(newElement: 4)
h.enqueue(newElement: 4)
h.isHomogeneous() // true
h.enqueue(newElement: 2)
h.isHomogeneous() // false
[/spoiler]
Subclassing a Generic Type
Swift has the ability to subclass generic classes. This can be useful in some cases, such as to create a concrete subclass of a generic class.
Add the following generic class to the playground:
class Box<T> {
// Just a plain old box.
}
Here you define a Box
class. The box can contain anything, and that’s why it’s a generic class. There are two ways you could subclass Box
:
- You might want to extend what the box does and how it works but keep it generic, so you can still put anything in the box;
- You might want to have a specialized subclass that always knows what’s in it.
Swift allows both. Add this to your playground:
class Gift<T>: Box<T> {
// By default, a gift box is wrapped with plain white paper
func wrap() {
print("Wrap with plain white paper.")
}
}
class Rose {
// Flower of choice for fairytale dramas
}
class ValentinesBox: Gift<Rose> {
// A rose for your valentine
}
class Shoe {
// Just regular footwear
}
class GlassSlipper: Shoe {
// A single shoe, destined for a princess
}
class ShoeBox: Box<Shoe> {
// A box that can contain shoes
}
You define two Box
subclasses here: Gift
and ShoeBox
. Gift
is a special kind of Box
, separated so that you may have different methods and properties defined on it, such as wrap()
. However, it still has a generic on the type, meaning it could contain anything. Shoe
and GlassSlipper
, a very special type of shoe, have been declared, and can be placed within an instance of ShoeBox
for delivery (or presentation to an appropriate suitor).
Declare instances of each class under the subclass declarations:
let box = Box<Rose>() // A regular box that can contain a rose
let gift = Gift<Rose>() // A gift box that can contain a rose
let shoeBox = ShoeBox()
Notice that the ShoeBox
initializer doesn’t need to take the generic type parameter anymore, since it’s fixed in the declaration of ShoeBox
.
Next, declare a new instance of the subclass ValentinesBox
— a box containing a rose, a magical gift specifically for Valentine’s Day.
let valentines = ValentinesBox()
While a standard box is wrapped with white paper, you’d like your holiday gift to be a little fancier. Add the following method to ValentinesBox
:
override func wrap() {
print("Wrap with ♥♥♥ paper.")
}
Finally, compare the results of wrapping both of these types by adding the following code to your playground:
gift.wrap() // plain white paper
valentines.wrap() // ♥♥♥ paper
ValentinesBox
, though constructed using generics, operates as a standard subclass with methods that may be inherited and overridden from a superclass. How elegant!
Enumerations With Associated Values
The queen is pleased with your work and wants to offer you a reward: Your choice of a generic treasure or a medal.
Add the following declaration to the end of your playground:
enum Reward<T> {
case treasureChest(T)
case medal
var message: String {
switch self {
case .treasureChest(let treasure):
return "You got a chest filled with \(treasure)."
case .medal:
return "Stand proud, you earned a medal!"
}
}
}
This syntax allows you to write an enum where at least one of the cases is a generic box. With the message
var, you can get the value back out. In the Result
example illustrated above, both the success and failure cases are generic with different types.
To get the associated value back out, use it like this:
let message = Reward.treasureChest("💰").message
print(message)
Congratulations and enjoy your reward!