Matua Doc

Matua Doc

Cheat sheet

Closure

How to spot a closure

If a function/method accepts a closure, the type annotation is (A...) -> B where:

  • (A...) is a set of parameter types. This could be:
    • a single parameter: (Int)
    • multiple parameters separated by commas: (String, String)
  • -> B denotes the return type.

For example, the Array.firstIndex(where:) method has the following signature:

func firstIndex(where predicate: (Self.Element) throws -> Bool) rethrows -> Self.Index?

In this method, the closure is predicate, broken up like this:

  1. A single parameter of the type Self.Element (this represents whatever element the array contains; in an array of Int, Self.Element is equivalent to Int)
  2. throws, meaning that the predicate is allowed to throw an exception.
  3. The method returns Self.Index?, an optional type representing the index of the element, if it exists.

Map

map(_:) applies a transformation to a collection using a closure, resulting in a new collection. Use it to process some data without affecting the original collection.

Note: in Swift, it is generally preferable to use let constants rather than var variables.

let numbers = [1, 5, 10, 25]

// Long syntax.
let doubled = numbers.map({ number in
    return number * 2
})

// Short syntax.
let doubled = numbers.map { $0 * 2 }

Filter

filter(_:) applies a filter to a collection using a closure, resulting in a new collection. The closure allows you to apply a Boolean expression to each element in the collection. Only if the closure returns true is the element included in the new collection.

let names = ["Alan", "James", "Ryan", "Sofia", "Benjy"]

// Long syntax.
let namesWithLetterS = names.filter({ name in
    return name.lowercased().contains("s")
})

// Short syntax.
let namesWithLetterS = names.filter { $0.lowercased().contains("s") }

Reduce

reduce(_:) lets you convert a collection into a single value of any type using a closure. You specify a starting value to the closure (i.e. a starting number like 0 or an empty string "") which is modified in each execution and passed to the next one.

let parts = ["John", "Jacob", "Jingleheimer", "Schmidt"]

// Long syntax.
let fullName = parts.reduce("", { result, part in
    return "\(result) \(part)"
})

// Short syntax.
let fullNameCount = parts.reduce(0) { $0 + $1.count }

For Super Players!

If a function accepts exactly two parameters of the same type, you can pass the function name instead of a closure. Mathematical operation (+, -, *, /) are functions under the hood and can be used for quick summing, subtraction, etc.

let fullNameCount = parts
    .map { $0.count }
    .reduce(0, +)

OOP: structs

In Swift, a struct is a data structure that contains properties (data) and methods (functions that operate on the data). There are also classes but we don’t use these in 13SWE.

In other languages, the words may be used differently. For example, in some languages:

  • a struct might data but not properties.
  • a property might refer to a specific method that returns a variable/constant rather than the variable/constant itself.
/// Structs have triple-slash comments.
struct StructName : ProtocolConformances {  // Structs use PascalCase (start with capital)
    /// Each property has a triple-slash comment.
    let someProperty: String

    /// Another triple-slash comment.
    var someOtherProperty: Int

    /// Methods have a triple-slash comment, then an empty line.
    ///
    /// Properties:
    /// - name: a description of what this property is for.
    /// - age: a description of what this property is for.
    ///
    /// Returns: whatever it returns, if it does.
    func someMethod(name: String, age: Int) -> Bool {
        // Function bodies contain double slash comments.
        return name.count >= 5 && age >= 11 && age < 25
    }
}

Creating objects (a.k.a. instances)

When you create an object, this is also called instantiation: the process of making instances of a struct.

struct Car {
    let make: String
    let model: String
    let year: Int
}

// This is one instance.
let junkPile = Car(make: "Reliant", model: "Robin", year: 1980)

// These are multiple instances, generated at random.
let makes = ["Ford", "Holden", "Mazda"]
let models = ["Junkpile", "Scrapmetal", "Hunkajunk"]
let cars = (1990...1995).map {
    Car(make: makes.random!, model: models.random!, year: $0)
}

Static properties

Properties marked with static belong to the struct, not to an instance. This is useful for data related to the struct that will never change. In your particular usage this year, think the kinds of constants you would make SCREAM_LIKE_THIS in Python!

struct Car {
    let make: String
    let model: String
    let year: Int

    // All cars go "beep". No exceptions.
    static let hornSound: String = "Beep!"

    /// Makes the horn go beep.
    func makeHornSound() {
        // This is an entirely pointless function, but hey…
        print(Car.hornSound)
    }
}

let junkPile = Car(make: "Reliant", model: "Robin", year: 1980)
junkPile.makeHornSound()  // Beep!

Static methods

Much like properties, methods can also be made static. They are called on the struct, not on an object/instance.

struct Car {
    let make: String
    let model: String
    let year: Int

    static let makes: [String] = ["Ford", "Holden", "Mazda"]
    static models: [String] = ["Junkpile", "Scrapmetal", "Hunkajunk"]

    /// A static method to create a random pile of crap.
    ///
    /// Returns: a utter mess of a car.
    static func rustPile() -> Car {
        return Car(
            make: Car.makes.randomElement()!,
            model: Car.models.randomElement()!,
            year: (1980...1990).randomElement()!
        )
    }
}

let terribleMotor = Car.rustPile()