Concrete values are the opposite of optional values in that they always represent a value (i.e. they are never nil ).

For example, Int is a concrete type whilst Int? is an optional type. To use Int? as a concrete type, it can be unwrapped using if let or guard let.

An enumeration (or enum) is a type that represents a range of possible options called cases. When a value is created based on an enumeration, it represents a single case. This is is a useful tool for representing a choice without needing to resort to magical strings or numbers.

Example: an enum with cases representing a range of colours. One colour is selected and stored in a constant.

enum Colour {
    case red
    case yellow
    case green
    case blue
}

let myFavouriteColour = Colour.blue

Cases can have associated values; these are additional pieces of information that can be stored with the selected case. There can be none, one, or more associated values which can be of any type.

Example: an enum of distance measurements.

enum Measurement {
    case none
    case kilometre(km: Double)
    case mile(mi: Double, nautical: Bool)
    case fuzzy(description: String)
}

let wgtnToNapier = Measurement.mile(mi: 168, nautical: false)

Associated values can be accessed using switch statements or if case/guard case.

switch wgtnToNapier {
    case .none: print("No distance specified.")
    case .kilometre(let km): print("\(km) km — or \(km * 0.62137119) mi")
    case .mile(let mi, let nautical):
        let km = mi * (nautical ? 1.852 : 1.609)
        let unitName = nautical ? "nautical " : ""
        print("\(mi) \(unitName)mi — or \(km) km")
    case .fuzzy(let description): print("Around \(fuzzy) away")
}

if case .none = wgtnToNapier {
    print("No distance specified")
}

guard case .kilometre(let km) = wgtnToNapier else { break }
print("\(km) km — or \(km * 0.62137119) mi")

Generics allow you to write flexible and reusable code by defining functions, types, and protocols that can work with any data type while maintaining type safety. Instead of writing multiple versions of a function for different types, generics let you write one function or type definition that adapts to different types. You specify a placeholder type, typically represented by a single uppercase letter like T, which gets replaced with an actual type when the code is used.

Example: a generic function for swapping two values. (This uses the inout keyword which means that the parameters are mutable)

func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var bill = "Bill"
var hank = "Hank"
swap(bill, hank)
print(bill)  // Hank

Here, T acts as a type placeholder, allowing the function to work with any type that supports assignment.

Generics are especially useful in collections, such as arrays and dictionaries which internally use generics to store elements of any type. For example, [Int] and [String] are both instances of the generic [Element] type; when you create an array, Element is swapped out for the appropriate concrete type. This ensures type safety because you can only store elements of a single specified type in the collection. Generics also play a key role in protocols with associated types, such as Comparable, which allows sorting different data types while ensuring that elements are of a consistent type.

Another powerful feature of Swift generics is generic constraints, which allow you to limit the types that can be used with a generic function or type. For example, if you want a function to work only with types that conform to the Equatable protocol, you can use T: Equatable.

Example: a (silly) function that only accepts structs that conform to Equatable.

func areEqual(_ lhs: T, with rhs: T) -> Bool { return lhs == rhs }

This ensures that the function can use == to compare values of type T. Similarly, generic classes and structs can be constrained to inherit from specific base classes or conform to protocols.

The term "magic numbers" refers to values whose use is seemingly unjustified except by a comment. That is to say, if a value requires a comment to explain what it means rather than being self-evident, it should probably be removed to improve the flexibility and robustness of the program.

For example, a number used in a calculation or condition might be considered magical if the number appears to make no sense. A 2 used when checking if a collection of numbers are even makes sense. On the other hand, some seemingly random number such as 324 used as a boundary may not make sense unless there is a comment to explain it.

In such a case, the number should be assigned to a constant with a meaningful name.

// Magical number.
if userInput >= 0 && userInput <= 324 { ... }  // 324 looks like magic.

// Flexible and robust version.
let maximumValue = 324
if userInput >= 0 && userInput <= maximumValue { ... }

Although it adds a line of code, it makes the code much clearer. Additionally, the maximum value in the range can be changed by adjusting that single constant rather than having to scan through any of the rest of the code to replace that value.

Equivalent to null in most languages or None in Python, this represents the absence of a value. If an optional value contains no value, it is equivalent to nil.

Values that either contain a concrete type or nil . When specifying types, optional values end with a question mark. For example, an optional String is denoted as String?.

Functions return optional values where it cannot be guaranteed that the function can return a valid value. For example, the Int() function (technically an initializer method for the Int type) can accept a String as an argument; therefore, Int() returns Int? just in case the string does not represent a valid integer.

Furthermore, structs and classes might define some attributes as optional in case they are not required for the rest of the object to function correctly.

To use optional values as concrete values, they need to be unwrapped using if let or guard let.

if let int = Int(string) {
    print(int + 5)
}

guard let double = Double(string) else {
    print("Invalid number.")
    return
}
print(double / 2)

Internally, this is represented as an enum containing either of two possible values: Optional.some(T) or Optional.none; however, Swift has special syntactic sugar to make handling Optional.none exactly the same as handling nil directly.

Safe unwrapping is used when you need to use optional values as concrete values. In Swift, the keywords are if let and guard let.

Use if let to create a new variable containing the value (if one exists) from an optional type. The constant can only be used inside that if statement's code block. If the optional value was empty (i.e. contained nil ), you can use an else statement to handle the absence of the value gracefully.

Example: converting a string to a number using if let. A message gets printed if the number is invalid.

if let number = Int(userInput) {
    print("You typed in \(userInput).")
} else {
    print("You did not enter a value.")
}

One limitation of if let is that the concrete value can only be used inside the if statement's code block. To be able to use the concrete value in the rest of the program at the current scope, use guard let instead.

Unlike if let, guard let requires you to handle the case when the optional value is nil. You must provide an else block straight away and the block must exit the current scope. To do so:

  • in top-level code, use exit(1) to quit the program. (Remember to import Foundation)
  • in a for or while loop, use break or continue:
    • break completely ends the loop, regardless of whether the condition has been met
    • continue stops the current iteration of the loop but continues with the next; this is useful for allowing for loops to completely finish.
    • Note: these also apply to .forEach

  • in functions:
    • a function that does not return a value, use return to prematurely exit the function
    • in functions that return a concrete value, return a constant
    • in functions that return an optional value, you can return a constant or nil

Example: converting a string to a number using guard let. The code is contained inside a function that returns an optional value; in case the provided string is not usable, it returns nil. Since readLine() returns a String?, this is also part of the guard let statement.

func intInput(_ prompt: String) -> Int? {
    print(prompt, terminator: "")
    guard let userInput = readLine(), let number = Int(userInput) else {
        print("Invalid value provided.")
        return nil
    }
    print("User entered \(number).")
    return number
}

Syntactic sugar is when a programming language provides syntax that make it easier to deal with. For example, the question mark used at the end of optional types (i.e. Int?) instead of typing the full type (Optional<Int>) is an example of syntactic sugar, shortening the amount of code you need to write.

Syntax is the set of rules that define the structure and arrangement of symbols, keywords, and punctuation in a programming language. It dictates how code must be written and formatted to be correctly interpreted and executed by a compiler or interpreter.

In Swift, all code in a project must have correct syntax or else the program will not run; on the other hand, Python only requires syntax be correct for lines of code that are actually run. This can mean bugs related to incorrect syntax might occur in Python code if and when certain sections of code are run; Swift eliminates this class of error.

A version of the map method whose closure returns an optional value rather than only a concrete value; this allows failable transformations to be skipped so that no erroneous value is added to the new collection.

The resulting collection's type is a collection of a concrete type with all nil values skipped.

let strings = ["1", "2.0", "banana", "4"]
let numbers = strings.compactMap { string in
    return Double(string)  // This returns Double? — the third array item is skipped
}
print(numbers)  // [1.0, 2.0, 4.0]

A method that filters items from one collection into a new collection based on a condition. The condition is evaluated inside a closure which must return Bool.

Example: filtering to include only numbers divisible by 2.

let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { number in
    return number.isMultiple(of: 2)
}
print(evenNumbers)  // [2, 4, 6]

A method that transforms all items from one collection into new values stored in a new collection. The transformation (that is, how the original values should be modified) is performed using a closure.

let numbers = [1, 5, 10, 15]
let doubled = numbers.map { number in
    return number * 2
}
print(doubled)  // [2, 10, 20, 30]

Functions are reusable blocks of code that perform a specific task and can be called with inputs (parameters) to return an output. They help organize code, reduce repetition, and improve readability and maintainability in programming.

In Swift, a function can have a signature or can be a closure. Either way, functions are also treated as a type, passed to functions or stored as an attribute in a struct .

Functions can accept parameters. In Swift, parameters have names in addition to the name of the variable used within the function itself. The parameter names are considered part of the signature.

A function that is defined as part of an object is called a method.

Closures are blocks of code usually passed as parameters to other function calls. For example, the transformation in map , filter , and reduce is achieved using closures.

An initializer (note the American spelling) is a special method in structs and classes

A function when it is defined as a behaviour within an object. Methods are called by adding a full stop after an object constant/variable or literal then calling the method like any other function.

Example: calling the uppercased method on a String constant (inside a call to print):

let name = "Anahera Madeupname"
print(name.uppercased())

Example: calling the isMultipleOf method on an Int literal (inside a call to print):

print(245.isMultipleOf(7))

Example: calling the forEach method, passing in a closure on a collection of numbers.

let numbers = [1, 2, 3, 4, 5]
numbers.forEach { number in
    print(number * 2)
}

See also: static methods.

A named function is the opposite of an anonymous function in that it is assigned a name either through a signature or being assigned to a constant/variable.

Example: both of the following functions are named input. The first uses a signature, the second is a closure assigned to a constant.

// Signature version.
func input(_ prompt: String) -> String? {
    print(prompt, terminator: "")
    return readLine()
}
let userAge = input("Enter your age: ")

// Closure version.
let input: (String) -> (String?) = { prompt in
    print(prompt, terminator: "")
    return readLine()
}
let userAge = input("Enter your age: ")

A function/method signature is the first line of a named function, defining:

Example: a function called input accepting a String parameter and returning an optional String:

func input(_ prompt: String) -> String?

Example: a throwing asynchronous function that returns an Int.

func intInput(_ prompt: String) async throws -> Int

In Swift, the parameter names are considered part of the signature. This helps to distinguish between signatures that share the same name. When condensing signatures down (i.e. for documentation or sharing information with others), the parameter names are included, separated by colons.

Example: a signature written as func myFunction(_ a: Int, b: Double, forC c: String) can be condensed to myFunction(_:b:forC).

-

-

-

-

-