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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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.

Queen and Jester iPhone

[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:

  1. 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;
  2. 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).

Cinderella like iPhone
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!