Swift 5.3 - Section 3: Building Your Own Types

Swift 5.3 - Section 3: Building Your Own Types

For this series of posts I have used extensively the book Swift Apprentice by Ray Wenderlich.
I strongly suggest this book, because if you buy the digital copy, Ray Wenderlich guarantees updates for the rest of the life!

I have also to say that Ray Wenderlich offers a cheat sheet available for download for free.

The purpose of these post is not to copy the book itself, that I strongly suggest to read, also doing the suggested exercises, that give the required confidence to really learn the language.
In particular I will not explain how to use Xcode or the playgrounds. The idea is that you buy the book and read it!

Final note: my background is deep experience in C#, so maybe some concepts are obvious for me. If you read this article, find it useful, but would like that I add something, don't hesitate to comment here below! But remember, my goal is not to copy the book :-)

Chapter 10: Structures

Your first structure

struct Location {
  let x: Int
  let y: Int
}

let storeLocation = Location(x: 2, y: 4)

Accessing members

Members are accessed with ".".
They can be modified if they are declared with "var" and the the object itself has been declared with "var".

Introducing methods

You can also add methods to structs.
Methods are also accessed with ".".

Structures as values

Structures are value types, i.e. its instances are copied on assignment.

Conforming to a protocol

Structures can conform to zero, one or more protocols.

Chapter 11: Properties

Stored properties

Stored properties are declared with let or var.
They are automatically added to the initializer created by Swift.

Default values

You can have a property with a default value:

struct Contact {
  var fullName: String
  let emailAddress: String
  var relationship = "Friend"
}

For a property with a default value, Swift will create the initializer also with a default value for this property.

Computed properties

Computed properties must be declared as a variable and must include a type:

struct TV {
  var height: Double
  var width: Double
  
  var diagonal: Int {
    let result = (height * height + width * width).squareRoot().rounded()
    return Int(result)
  }
}

Getter and setter

It's possible to have both getter and setter (in the setter the new value variable is called newValue):

var diagonal: Int {
  get {
    let result = (height * height + width * width).squareRoot().rounded()
    return Int(result)
  }
  set {
    let ratioWidth = 16.0
    let ratioHeight = 9.0
    let ratioDiagonal = (ratioWidth * ratioWidth + ratioHeight * ratioHeight).squareRoot()
    height = Double(newValue) * ratioHeight / ratioDiagonal
    width = height * ratioWidth / ratioHeight
  }
}

Note that set-only properties are not allowed.

Type properties

They are static properties, and can be accessed only by the type:

struct Level {
  static var highestLevel = 1
  let id: Int
  var boss: String
  var unlocked: Bool
}

let highestLevel = Level.highestLevel

Property observers

willSet and didSet are only available for stored properties (if you want to listen to changes to a computed property, add the code to the property's setter). They aren't called during initializers.
willSet can use newValue and didSet can use oldValue.
In the didSet it's possible to revert to the old value, but then willSet and didSet won't be called anymore.

struct Level {
  static var highestLevel = 1
  let id: Int
  var boss: String
  var unlocked: Bool {
    willSet {
      print ("The value will be set to \(newValue)")
    }
    didSet {
      if unlocked && id > Self.highestLevel {
        Self.highestLevel = id
      } else {
        unlocked = oldValue
      }
    }
  }
}

As they are not called during initialization, in practice they are useful only for variable properties, but not for constant properties.

Lazy properties

Lazy properties are stored properties, whose value is not calculated during initialization, but only after the first use (and note after the first initialization, it is not computed anymore):

struct Circle {
  lazy var pi = {
    ((4.0 * atan(1.0 / 5.0)) - atan(1.0 / 239.0)) * 4.0
  }()
  var radius = 0.0
  var circumference: Double {
    mutating get {
      pi * radius * 2
    }
  }
  init(radius: Double) {
    self.radius = radius
  }
}

Notes:

  • the lazy properties must be declared with var, even if its values changes only once, during first use (and not during initialization);
  • the circumference getter must be declared with mutable because the value of pi changes;
  • as pi is a stored property, you need a custom initializer to use only the radius.

Chapter 12: Methods

Introducing self

self points to the current instance.

Introducing initializers

Initializers are special methods called to create a new instance.
They omit the func keyword and they don't have a return value.

struct SimpleDate {
  var month: String
  
  init() {
    month = "January"
  }
  
  func monthsUntilWinterBreak() -> Int {
    months.firstIndex(of: "December")! - months.firstIndex(of: month)!
  }
}

Initializers in structures

Initializers must ensure all properties are set before the instance is ready to use.
If you add a custom initializer to a structure, you loose the automatic initializer, that by default received a parameter for each property.

Default values and initializers

A faster way to initialize properties is to use default values.
In this way, you don't need a custom initializer. You can still use the default initializer, and override the default values if you need.

Introducing mutating methods

Methods in structures can't change the values of properties without being marked as mutating:

mutating func advance() {
  day += 1
}

For example, this will prevent the compiler to call this method on constants.

Type methods

They are static functions:

struct Math {
  static func factorial(of number: Int) -> Int {
    (1...number).reduce(1, *)
  }
}

Math.factorial(of: 6) // 720

Adding to an existing structure with extensions

It's possible to add methods to existing structures, even when their source code is not available:

extension Math {
  static func primeFactors(of value: Int) -> [Int] {
    var remainingValue = value
    
    var testFactor = 2
    var primes: [Int] = []
    
    while testFactor * testFactor <= remainingValue {
      if remainingValue % testFactor == 0 {
        primes.append(testFactor)
        remainingValue /= testFactor
      } else {
        testFactor += 1
      }
    }
    if remainingValue > 1 {
      primes.append(remainingValue)
    }
    return primes
  }
}

Math.primeFactors(of: 81)  // [3, 3, 3, 3]

Keeping the compiler-generated initializer using extensions

Generally, if you write your own custom initializer, you loose the default initializer.
In reality, it's possible to keep the default initializer, and write the custom initializer as an extension method:

struct SimpleDate {
  var month = "January"
  var day = 1
  
  func monthsUntilWinter() -> Int {
    months.firstIndex(of: "December)! - months.firstIndex(of: month)!
  }
  
  mutating func advance() {
    day += 1
  }
}

extension SimpleDate {
  init(month: Int, day: Int) {
    self.month = months[month-1]
    self.day = day
  }
}

Chapter 13: Classes

Creating classes

A class is very similar to a struct, but it doesn't provide a memberwise initializer automatically, so you need to initialize all fields explicitly:

class Person {
  var firstName: String
  var lastName: String
  
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  
  var fullName: String {
    "\(firstName) \(lastName)"
  }
}

let john = Person(firstName: "Johnny", lastName: "Appleseed")

Reference types

While structures are values types, classes are reference types.

Object identity

The == operator checks if two values are equal.
The === operator compares the memory address of the two references.

Methods and mutability

With reference types, the value is a reference to an instance.
So you can't re-assign it to another instance, but you can indeed change the referenced object (and without using the mutating keyboard).

Extending a class using an extension

The same extension available for structures is also available for classes:

extension Student {
  var fullName: String {
    "\(firstName) \(lastName)"
  }
}

Chapter 14: Advanced Classes

Introducing inheritance

struct Grade {
  var letter: Character
  var points: Double
  var credits: Double
}

class Person {
  var firstName: String
  var lastName: String
  
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
}

class Student: Person {
  var grades: [Grade] = []
  
  func recordGrade(_ grade: Grade) {
    grades.append(grade)
  }
}

Rules for inheritance:

  • a class can inherit from only one other class (single inheritance);
  • there is no limit to the depth of subclassing.

Polymorphism

func phonebookName(_ person: Person) -> String {
  "\(person.lastName), \(person.firstName)"
}

let person = Person(firstName: "Johnny", lastName: "Appleseed")
let oboePlayer = OboePlayer(firstName: "Jane", lastName: "Appleseed")

phonebookName(person) // Appleseed, Johnny
phonebookName(oboePlayer) // Appleseed, Jane

Runtime hierarchy checks

as operator is used to treat a property or variable to another type:

  • as: cast to a super type, so it will always succeed;
  • as?: cast to a down type, so it might fail;
  • as!: cast to a down type, forced (if it fails, the application will crash).

as? can be used with let guards:

if let hallMonitor = hallMonitor as? BandMember {
  print("This hall monitor is a band member and practices at least \(hallMonitor.minimumPracticeTime) hours per week.")
}

Inheritance, methods and overrides

class StudentAthlete: Student {
  var failedClasses: [Grade] = []
  
  override func recordGrade(_ grade: Grade) {
    super.recordGrade(grade)
    
    if grade.letter == "F" {
      failedClasses.append(grade)
    }
  }
  
  var isEligible: Bool {
    failedClasses.count < 3
  }
}

Introducing super

super points to the nearest base class of the current class.

Preventing inheritance

To disallow subclasses uses final:

final class FinalStudent: Person {}
class FinalStudentAthlete: FinalStudent {} // Build error!

It's also possible to mark single methods with final, to allow the class to have subclasses, but to avoid that this particular method gets overridden:

class AnotherStudent: Person {
  final func recordGrade(_ grade: Grade) {}
}

class AnotherStudentAthlete: AnotherStudent {
  override func recordGrade(_ grade: Grade) {} // Build error!
}

Inheritance and class initialization

Initializers in subclasses are required to call super.init in their initializers before they leave:

class StudentAthlete: Student {
  var sports: [String]
  
  init(firstName: String, lastName: String, sports: [String]) {
    self.sports = sports
    super.init(firstName: firstName, lastName: lastName)
  }
  // original code
}

Two phases initialization

  • Phase one: initialize all stored properties in the inherited class; you can't use properties or methods during this phase;
  • Phase two: now you can use properties and methods and initializations that require the use of self.

Required and convenience initializers

You can define an initializer as required:

class Student {
  let firstName: String
  let lastName: String
  var grades: [Grade] = []
  
  required init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  // original code
}

So in a derived class, now you still must provide this initializer:

class StudentAthlete: Student {
  // Now required by the compiler!
  required init(firstName: String, lastName: String) {
    self.sports = []
    super.init(firstName: firstName, lastName: lastName)
  }
  // original code
}

You can also make an initializer as convenience, i.e. an initializer that calls a designated initializer (and they are subject to the same two-phase rule initialization):

class Student {
  convenience init(transfer: Student) {
    self.init(firstName: transfer.firstName, lastName: transfer.lastName)
  }
  // original code
}

Understanding the class lifecycle

Swift uses reference counting for deciding when to clean up unused objects (automatic reference counting or ARC).

Deinitialization

A deinitializer is a special method that is run when the object reference count reaches zero, just before it is removed from memory:

class Person {
  // original code
  deinit {
    print("\(firstName) \(lastName) is being removed from memory!")
  }
}

deinit isn't required and is automatically invoked by Swift.

Retain cycles and weak references

In case you have two object referencing to each other, they would not get freed.

class Student: Person {
  weak var partner: Student?
  // original code
}

A weak reference allows to reference another object without increasing its reference count.
A weak reference must be declared as optional, so then when the referenced object gets deallocated, its value will become nil.

Chapter 15: Enumerations

Declaring an enumeration

enum Month {
  case january
  case february
  case march
  case april
  case may
  case june
  case july
  case august
  case september
  case october
  case november
  case december
}

It's also possible to write all values on a single line, comma separated:

enum Month {
  case january, february, march, april, may, june, july, august, september, october, november, december
}

Use:

let month = Month.january
let month: Month = .january

Deciphering an enumeration in a function

You can use the enum in a switch:

  • removing the enum name, because implicit;
  • removing the default clause if all cases are already covered.
func semester(for month: Month) -> String {
  switch month {
    case .august, .september, .october, .november, .december:
      return "Autumn"
    case .january, .february, .march, .april, .may:
      return "Spring"
    case .june, .july:
      return "Summer"
  }
}

Raw values

By default enums are not backed by integers, but it's possible to add it:

enum Month: Int {
  ...
}

and it's possible to give an initial value different from 0:

enum Month: Int {
  case january = 1, february, march, april, may, june, july, august, september, october, november, december
}

Accessing the raw value

func monthsUntilWinterBreak(from month: Month) -> Int {
  Month.december.rawValue - month.rawValue
}
monthsUntilWinterBreak(from: .april) // 8

Initializing with the raw value

Initializing with the raw value gives an optional because the raw value could miss.
If you're sure that the value is allowed, you can force unwrap the optional:

let fifthMonth = Month(rawValue: 5)!
monthsUntilWinterBreak(from: fifthMonth) // 7

String raw values

You can also have a raw value of type string, so the conversion to string is done automatically with rawValue.
Note also that you can add a property to an enum:

enum Icon: String {
  case music
  case sports
  case weather
  
  var filename: String {
    "\(rawValue).png"
  }
}

let icon = Icon.weather
icon.filename // weather.png

It's also possible to give other string values explicitly:

enum Suit: String {
  case heart = "suit.heart.fill"
  case club = "suit.club.fill"
  case spade = "suit.spade.fill"
  case diamond = "suit.diamond.fill"
}

let image = Image(systemName: Suit.heart.rawValue).resizable()

Unordered raw values

enum Coin: Int {
  case penny = 1
  case nickel = 5
  case dime = 10
  case quarter = 25
}

let coin = Coin.quarter
coin.rawValue // 25

Associated values

They let to associate a custom value (or values) to each enumeration case.
The number and types of the associated values can be different between cases.
You can't have both raw values and associated values for the same enum.

enum WithdrawalResult {
  case success(newBalance: Int)
  case error(message: String)
}

func withdraw(amount: Int) -> WithdrawalResult {
  if amount <= balance {
    balance -= amount
    return .success(newBalance: balance)
  } else {
    return .error(message: "Not enough money!")
  }
}

let result = withdraw(amount: 99)
switch result {
  case .success(let newBalance):
    print("Your new balance is: \(newBalance)")
  case .error(let message):
    print(message)
}

Another example with http verbs:

enum HTTPMethod {
  case get
  case post(body: String)
}

let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
  fatalError("No message was posted")
}
print(body)

Iterating through all cases

To be allowed to iterate through all cases, the enum must conform to the CaseIterable protocol:

enum Pet: CaseIterable {
  case cat, dog, bird, turtle, fish, hamster
}

for pet in Pet.allCases {
  print(pet)
}

Enumerations without any cases

If you need to create a namespace to host a group of related methods, you can use enum so that you can't create an instance of it:

enum Math {
  static func factorial(of number: Int) -> Int {
    (1...number).reduce(1, *)
  }
}
let factorial = Math.factorial(of: 6) // 720

let math = Math() // ERROR: No accessible initializers

Enums can have custom initializers, computed properties and methods.
To create an instance, you must assign a member value.
If you don't add any member value, you can't create an instance.

Optionals

Optionals are in reality enumerations with two cases:

  • .none if there is no value;
  • .some if there is a value, and the value is attached as an associated value.
var age: Int?
age = 17
age = nil

switch age {
  case .none:
    print("No value")
  case .some(let value):
    print("Got a value: \(value)")
}

In reality Swift hides the enum:

let optionalNil: Int? = .none
optionalNil == nil // true
optionalNil == .none // true

Chapter 16: Protocols

Introducing protocols

A protocol doesn't contain any implementation and can't be instantiated.

protocol Vehicle {
  func accelerate()
  func stop()
}

let vehicle = new Vehicle() // Error

Protocol syntax

A class adopting a protocol must implement its methods:

class Unicycle: Vehicle {
  var peddling = false
  
  func accelerate() {
    peddling = true
  }
  
  func stop() {
    peddling = false
  }
}

Structs and enumerations can also conform to protocols.

Methods in protocols

Methods in protocols can't contain implementations and can't contain default parameters:

protocol OptionalDirectionVehicle {
  // Build error!
  func turn(_ direction: Direction = .left)
}

To provide a default value, you must define both versions of the method explicitly:

protocol OptionalDirectionVehicle {
  func turn()
  func turn(_ direction: Direction)
}

Properties in protocols

You can also define properties in a protocol, without specifying if they are stored or computed (only get/set is provided):

protocol VehicleProperties {
  var weight: Int { get }
  var name: String { get set }
}

Initializers in protocols

Protocols can't be initialized, but can declare initializers that the conforming types should have:

protocol Account {
  var value: Double { get set }
  init(initialAmount: Double)
  init?(transferAccount: Account)
}

The conforming type will need to declare the initializers with the required keyword:

class BitcoinAccount: Account {
  var value: Double
  required init(initialAmount: Double) {
    value = initialAmount
  }
  required init?(transferAccount: Account) {
    guard transferAccount.value > 0.0 else {
      return nil
    }
    value = transferAccount.value
  }
}

var accountType: Account.Type = BitcoinAccount.self
let account = accountType.init(initialAmount: 30.00)
let transferAccount = accountType.init(transferAccount: account)!

Protocol inheritance

You can declare a protocol inheriting from another protocol:

protocol WheeledVehicle: Vehicle {
  var numberOfWheels: Int { get }
  var wheelSize: Double { get set }
}

Associated types in protocols

You can add an associated type as a protocol member.

protocol WeightCalculatable {
  associatedtype WeightType
  var weight: WeightType { get }
}

class HeavyThing: WeightCalculatable {
  // This heavy thing only needs integer accuracy
  typealias WeightType = Int
  
  var weight: Int { 100 }
}

class LightThing: WeightCalculatable {
  // This light thing needs decimal places
  typealias WeightType = Double
  
  var weight: Double { 0.0025 }
}

Implementing protocols

When you declare your type conforming to a protocol, you must implement all the requirements declared in the protocol:

class Bike: Vehicle {
  var peddling = false
  var brakesApplied = false
  
  func accelerate() {
    peddling = true
    brakesApplied = false
  }
  
  func stop() {
    peddling = false
    brakesApplied = true
  }
}

Implementing properties

You must implement all the properties defined in the protocol:

class Bike: WheeledVehicle {
  let numberOfWheels = 2
  var sheelSize = 16.0
  
  var peddling = false
  var brakesApplied = false
  
  func accelerate() {
    peddling = true
    brakesApplied = false
  }
  
  func stop() {
    peddling = false
    brakesApplied = true
  }
}

Associated types in protocols

Sometimes, in your protocol, you want to define that the implement will need to define a type used in the protocol itself (to be chosen at implementation time). So you define it in your protocol as an associated type:

protocol WeightCalculable {
  associatedtype WeightType
  var weight: WeightType { get }
}

Then in the implementation you will specify it via a typealias:

class HeavyThing: WeightCalculable {
  typealias WeightType = Int
  
  var weight: Int { 100 }
}

class LightThing: WeightCalculable {
  typealias WeightType = Double
  
  var weight: Double { 0.0025 }
}

Note that this prevents to use the protocol as a simple variable type, because the compiler doesn't know the type of the associated type. For this, you can use generic constraints.

Implementing multiple protocols

A class can only inherit from anther class, but can conform to multiple protocols.

protocol Wheeled {
  var numberOfWheels: Int { get }
  var wheelSize: Double { get set }
}

class Bike: Vehicle, Wheeled {
  // Implement both Vehicle and Wheeled
}

Protocol composition

You can declare a function parameter as conforming to multiple protocols:

func roundAndRound(transportation: Vehicle & Wheeled) {
  transportation.stop()
  print("The brakes are being applied to \(transportation.numberOfWheels) wheels.")
}

roundAndRound(transportation: Bike())
// The brakes are being applied to 2 wheels.

Extensions & protocol conformance

You can also adopt protocols using extensions.
The example below adds the Reflective protocol to the String class, even you don't have its source codes:

protocol Reflective {
  var typeName: String { get }
}

extension String: Reflective {
  var typeName: String {
    "I’m a String"
  }
}

let title = "Swift Apprentice!"
title.typeName // I’m a String

You can also add a protocol adoption via extension:

class AnotherBike: Wheeled {
  var peddling = false
  let numberOfWheels = 2
  var wheelSize = 16.0
}

extension AnotherBike: Vehicle {
  func accelerate() {
    peddling = true
  }
  
  func stop() {
    peddling = false
  }
}

So if you want to remove the Vehicle protocol from AnotherBike, you simply remove the extension.
Note that in extensions it's not possible to declare stored properties.

Requiring reference semantics

How to require that a protocol is adopted only by reference types:

protocol Named: class {
  var name: String { get set }
}

Equatable

If you define a class, you can't compare two instances with ==:

class Record {
  var wins: Int
  var losses: Int

  init(wins: Int, losses: Int) {
    self.wins = wins
    self.losses = losses
  }
}

let recordA = Record(wins: 10, losses: 5)
let recordB = Record(wins: 10, losses: 5)

recordA == recordB // Build error!

To use the == operator, you must conform to the Equatable protocol:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

Note that Self with capital letter means the type of the current instance.

So we can add it to the previous class:

extension Record: Equatable {
  static func ==(lhs: Record, rhs: Record) -> Bool {
    lhs.wins == rhs.wins && lhs.losses == rhs.losses
  }
}

recordA == recordB // true

Comparable

Comparable is a subprotocol of Equatable:

protocol Comparable: Equatable {
  static func <(lhs: Self, rhs: Self) -> Bool
  static func <=(lhs: Self, rhs: Self) -> Bool
  static func >=(lhs: Self, rhs: Self) -> Bool
  static func >(lhs: Self, rhs: Self) -> Bool
}

In reality it is enough to implement only the < operator, because the others are provided by the standard library:

extension Record: Comparable {
  static func <(lhs: Record, rhs: Record) -> Bool {
    if lhs.wins == rhs.wins {
      return lhs.losses > rhs.losses
    }
    return lhs.wins < rhs.wins
  }
}

"Free" functions

If your class conforms to the Comparable protocol, you get additional functions on arrays:

let teamA = Record(wins: 14, losses: 11)
let teamB = Record(wins: 23, losses: 8)
let teamC = Record(wins: 23, losses: 9)
var leagueRecords = [teamA, teamB, teamC]

leagueRecords.sort()
// {wins 14, losses 11}
// {wins 23, losses 9}
// {wins 23, losses 8}

leagueRecords.max() // {wins 23, losses 8}
leagueRecords.min() // {wins 14, losses 11}
leagueRecords.starts(with: [teamA, teamC]) // true
leagueRecords.contains(teamA) // true

Hashable

Hashable is a subprotocol of Equatable and is required for any type used as a Dictionary key (generated automatically for value types by the compiler).

class Student {
  let email: String
  let firstName: String
  let lastName: String
  
  init(email: String, firstName: String, lastName: String) {
    self.email = email
    self.firstName = firstName
    self.lastName = lastName
  }
}

extension Student: Hashable {
  static func ==(lhs: Student, rhs: Student) -> Bool {
    lhs.email == rhs.email && lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
  }
  
  func hash(into hasher: inout Hasher) {
    hasher.combine(email)
    hasher.combine(firstName)
    hasher.combine(lastName)
  }
}

Now you can use Student as a Dictionary key:

let john = Student(email: "johnny.appleseed@apple.com", firstName: "Johnny", lastName: "Appleseed")
let lockerMap = [john: "14B"]

Identifiable

This protocol provides an unique id property, that bust be of a type conforming to Hashable:

extension Student: Identifiable {
  var id: String {
    email
  }
}

CustomStringConvertible

This protocol formats a type to string for debug and logging:

protocol CustomStringConvertible {
  var description: String { get }
}

Example:

extension Student: CustomStringConvertible {
  var description: String { "\(firstName) \(lastName)" }
}

print(john)
// Johnny Appleseed

CustomDebugStringConvertible

Like CustomStringConvertible, but it has a debugDescription, used by debugPrint(), used only in Debug configurations.

Chapter 17: Generics

Using type parameters

class Cat {
  var name: String
  
  init(name: String) {
    self.name = name
  }
}

class Dog {
  var name: String
  
  init(name: String) {
    self.name = name
  }
}

class Keeper<Animal> {
  var name: String
  var morningCare: Animal
  var afternoonCare: Animal

  init(name: String, morningCare: Animal, afternoonCare: Animal) {
    self.name = name
    self.morningCare = morningCare
    self.afternoonCare = afternoonCare
  }
}

let jason = Keeper(name: "Jason",
  morningCare: Cat(name: "Whiskers"),
  afternoonCare: Cat(name: "Sleepy"))

Type constraints

It's possible to give constraints on the types used for generics:

protocol Pet {
  var name: String { get } // all pets respond to a name
}
extension Cat: Pet {}
extension Dog: Pet {}

class Keeper<Animal: Pet> {
  /* definition body as before */
}

It's also possible to specify constraints to extensions:

extension Array where Element: Cat {
  func meow() {
    forEach { print("\($0.name) says meow!") }
  }
}

You can also specify that a type should conform to a protocol only if it meets certain constraints, via conditional conformance:

protocol Meowable {
  func meow()
}

extension Cat: Meowable {
  func meow() {
    print("\(self.name) says meow!")
  }
}

extension Array: Meowable where Element: Meowable {
  func meow() {
    forEach { $0.meow() }
  }
}

Arrays

[] is a syntax sugar:

let animalAges: [Int] = [2,5,7,9]
let animalAges: Array<Int> = [2,5,7,9]

Dictionaries

Dictionaries have two type parameters:

struct Dictionary<Key: Hashable, Value> // etc..

let intNames: Dictionary<Int, String> = [42: "forty-two"]

let intNames2: [Int: String] = [42: "forty-two", 7: "seven"]
let intNames3 = [42: "forty-two", 7: "seven"]

Optionals

Optionals are enumerations but also generics:

enum OptionalDate {
  case none
  case some(Date)
}

enum OptionalString {
  case none
  case some(String)
}

struct FormResults {
  // other properties here
  var birthday: OptionalDate
  var lastName: OptionalString
}

enum Optional<Wrapped> {
  case none
  case some(Wrapped)
}

var birthdate: Optional<Date> = .none
if birthdate == .none {
  // no birthdate
}


var birthdate: Date? = nil
if birthdate == nil {
  // no birthdate
}

Generic function parameters

Functions can be generic as well:

func swapped<T, U>(_ x: T, _ y: U) -> (U, T) {
  (y, x)
}

swapped(33, "Jay") // returns ("Jay", 33)