Learning Swift — Days 111-113

Review of the Swift Programming Language

Day 111

  • Repeat-while loops
  • Conditional Statements
    • If (in case conditions are not complete, no action will be taken and this is fine)
    • Switch (this has to be exhaustive instead)
      • No Implicit Fallthrough
      • Tuples: when using tuples inside switch blocks we can use the wildcard pattern (_ in place of one of the values of the tuple) to match any possible value. If multiple matches are possible, the first matching case is always used.
      • Value Bindings: this is a very powerful feature. A switch case can name the value or values it matches to temporary constants or variables, for use in the body of the case.
      • Where: an example of this is case let (x, y) where x == y:
      • Compound Cases: if any of the patterns match, then the case is considered to match. Compound cases can also include value bindings, in which case there is a small rule to follow: all of the patterns of a compound case have to include the same set of value bindings, and each binding has to get a value of the same type from all of the patterns in the compound case.
  • Control Transfer Statements: control transfer statements change the order in which our code is executed, by transferring control from one piece of code to another. Swift has five control transfer statements: continue, break, fallthrough, return, throw.
    • The continue statement tells a loop to stop what it is doing and start again at the beginning of the next iteration through the loop.
    • The break statement ends execution of an entire control flow statement immediately.
  • Labeled Statements: you can mark a loop statement or conditional statement with a statement label. A labeled statement is indicated by placing a label on the same line as the statement’s introducer keyword, followed by a colon. This is an example code for realising the Snakes and Ladders game:
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08

var square = 0
var diceRoll = 0

gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
    case finalSquare:
        // diceRoll will move us to the final square, so the game is over
        break gameLoop
    case let newSquare where newSquare > finalSquare:
        // diceRoll will move us beyond the final square, so roll again
        continue gameLoop
    default:
        // this is a valid move, so find out its effect
        square += diceRoll
        square += board[square]
    }
}
print("Game over!")

If the break statement above did not use the gameLoop label, it would break out of the switch statement, not the while statement. Using the gameLoop label makes it clear which control statement should be terminated.

  • Early Exit: this describes the usage of the guard statement.
  • Checking API Availability:
if #available(platform name version, ..., *) {
    statements to execute if the APIs are available
} else {
    fallback statements to execute if the APIs are unavailable
}

Functions

Some terminology:

  • defining and calling functions! Not declaring!
  • Parameters are typed values the ruction takes as input, but when we call it they are arguments!

  • Functions Parameters and Return Values
    • Functions Without Parameters
    • Functions With Multiple Parameters
    • Functions Without Return Values: Functions without a defined return type return a special value of type Void. This is simply an empty tuple, which is written as (). Now, this is interesting! Conversely, the part where it is explained that “the return value of a function can be ignored when it is called” is murky at its best…
    • Functions with Multiple Return Values
    • Optional Tuple Return Types
  • Function Argument Labels and Parameter Names: each function parameter has both an argument label and a parameter name. The argument label is used when calling the function; each argument is written in the function call with its argument label before it. The parameter name is used in the implementation of the function. By default, parameters use their parameter name as their argument label. To remember this distinction I think (a)rgument comes before (p)arameter and so stays outside! Mental mapping FTW!
    • Specifying Argument Labels
    • Omitting Argument Labels
    • Default Parameter Values: place parameters that have default values at the end of the parameter list.
    • Variadic Parameters: a variadic parameter accepts zero or more values of a specified type. We use a variadic parameter to specify that the parameter can be passed a varying number of input values when the function is called. Write variadic parameters by inserting three period characters (...) after the parameter’s type name. The values passed to a variadic parameter are made available within the function’s body as an array of the appropriate type. A function may have at most one variadic parameter.
    • In-Out Parameters: function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error.
      • If we want a function to modify a parameter’s value, and we want those changes to persist after the function call has ended, we need to define that parameter as an in-out parameter instead.
      • We can only pass a variable as the argument for an in-out parameter.
      • We place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.
      • In-out parameters cannot have default values, and variadic parameters cannot be marked as inout.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
  • Function Types: every function has a specific function type, made up of the parameter types and the return type of the function.
    • Using Function types
    • Function Types as Parameter Types
    • Function Types as Return Types
  • Nested Functions

Day 112

Just spent the whole day of yesterday to try and see what was happening on my Mac due to an issue with OneDrive. This morning I called in Apple Care and they immediately escalated the issue and made me send diagnostic information for system and Time Machine. This is the first time ever that from Apple Care someone asks me to interact with the Terminal. For sure this must be bad. Never mind… let’s try to move on with my learning as tonight is WWDC’s opening night and I will for sure follow it.

Closures

  • Closures are self-contained blocks of functionality that can be passed around and used in our code. Closures can capture and store references to any constants and variables from the context in which they are defined. This is known as closing over those constants and variables. Swift handles all of the memory management of capturing for us.
    • Global and nested functions, are actually special cases of closures. Closures take one of three forms:
      1. Global functions are closures that have a name and do not capture any values.
      2. Nested functions are closures that have a name and can capture values from their enclosing function.
      3. Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.
  • Closure Expressions: closure expressions are a way to write inline closures in a brief, focused syntax.
    • The Sorted Method: here we see how to get from a “normal function” to a proper closure
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)

For characters in strings, “greater than” means “appears later in the alphabet than”.

  • Closure Expression Syntax: Closure expression syntax has the following general form:
{ (parameters) -> return type in
    statements
}

Which gives us the following result for the same sorting idea:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
	return s1 > s2
})

// or even on a single line 
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
  • Inferring Type from Context: because the sorting closure is passed as an argument to a method, Swift can infer the types of its parameters and the type of the value it returns.
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })
  • Implicit Returns from Single-Expression Closures: single-expression closures can implicitly return the result of their single expression by omitting the return keyword from their declaration.
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })
  • Shorthand Argument Names: Swift automatically provides shorthand argument names to inline closures, which can be used to refer to the values of the closure’s arguments by the names $0, $1, $2, and so on.
reversedNames = names.sorted(by: { $0 > $1 } )
  • Operator Methods: there’s actually an even shorter way to write the closure expression above.
reversedNames = names.sorted(by: >)
  • Trailing Closures: if we need to pass a closure expression to a function as the function’s final argument and the closure expression is long, it can be useful to write it as a trailing closure instead. A trailing closure is written after the function call’s parentheses, even though it is still an argument to the function. When we use the trailing closure syntax, we don’t write the argument label for the closure as part of the function call.
reversedNames = names.sorted() { $0 > $1 }

// if the closure is the only argument of the method
reversedNames = names.sorted { $0 > $1 }
  • Using the map(_:) method:
let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

// We can now use the numbers array to create an array of String values, by passing a closure expression to the array’s map(_:) method as a trailing closure:
let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}

// The map(_:) method calls the closure expression once for each item in the array.
  • Capturing Values
  • Closures Are Reference Types
  • Escaping Closures: a closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.
  • Autoclosures: an autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it. An autoclosure lets you delay evaluation, because the code inside isn’t run until we call the closure.

These last two parts are quite obscure and I would like to see them in action in code to see what they do and why and HOW!

Enumerations

  • Enumeration Syntax
  • Matching Enumeration Values with a Switch Statement
  • Iterating over Enumeration Cases
  • Associated Values: it’s sometimes useful to be able to store values of other types alongside these case values. This additional information is called an associated value, and it varies each time we use that case as a value in our code. For example:
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
  • Raw Values: Raw values can be strings, characters, or any of the integer or floating-point number types. Each raw value must be unique within its enumeration declaration.
    NOTE
    Raw values are not the same as associated values. Raw values are set to prepopulated values when we first define the enumeration in your code, like the three ASCII codes above. The raw value for a particular enumeration case is always the same. Associated values are set when you create a new constant or variable based on one of the enumeration’s cases, and can be different each time we do so.
  • Implicitly Assigned Raw Values: when strings are used for raw values, the implicit value for each case is the text of that case’s name.
  • Initialising from a Raw Value
  • Recursive Enumerations: a recursive enumeration is an enumeration that has another instance of the enumeration as the associated value for one or more of the enumeration cases. We indicate that an enumeration case is recursive by writing indirect before it, which tells the compiler to insert the necessary layer of indirection.

Day 113

Structures and Classes

WWDC Keynote was yesterday and it was full of interesting things. I just need to keep calm and focused and move on with my things. There is no need to rush with beta things. I may be able to get my hands on Xcode 11 beta but I am not yet sure about it. Let’s continue with the review program.

  • Comparing Structures and Classes
    • Both can:
      1. Define properties to store values
      2. Define methods to provide functionality
      3. Define subscripts to provide access to their values using subscript syntax
      4. Define initialisers to set up their initial state
      5. Be extended to expand their functionality beyond a default implementation
      6. Conform to protocols to provide standard functionality of a certain kind
    • Classes have, in addition:
      1. Inheritance, that enables one class to inherit the characteristics of another.
      2. Type casting, to check and interpret the type of a class instance at runtime.
      3. Deinitializers, that enable an instance of a class to free up any resources it has assigned.
      4. Reference counting, to allow more than one reference to a class instance.
  • Definition Syntax
  • Structure and Class Instances
  • Accessing Properties
  • Memberwise Initialisers for Structure Types

  • Structures and Enumerations Are Value Types: just to be sure we do not forget, a value type is a type whose value is copied when it’s assigned to a variable or constant, or when it’s passed to a function.
  • Classes Are Reference Types: unlike value types, reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used.
    • Identity Operators
    • Pointers

Properties

Computed properties are provided by classes, structures, and enumerations. Stored properties are provided only by classes and structures.

  • Stored Properties
    • Stored Properties of Constant Structure Instances: if we create an instance of a structure and assign that instance to a constant, we cannot modify the instance’s properties, even if they were declared as variable properties.
    • Lazy Stored Properties: a lazy stored property is a property whose initial value is not calculated until the first time it is used. We indicate a lazy stored property by writing the lazy modifier before its declaration.
    • Stored Properties and Instance Variables
  • Computed Properties: they provide a getter and an optional setter to retrieve and set other properties and values indirectly. Not really sure what getter and setter are, but I have some ideas about it!
    • Shorthand Setter Declaration: if a computed property’s setter doesn’t define a name for the new value to be set, a default name of newValue is used.
    • Shorthand Getter Declaration: if the entire body of a getter is a single expression, the getter implicitly returns that expression.
    • Read-Only Computed Properties: a computed property with a getter but no setter is known as a read-only computed property. A read-only computed property always returns a value, and can be accessed through dot syntax, but cannot be set to a different value.
  • Property Observers: property observers observe and respond to changes in a property’s value. Property observers are called every time a property’s value is set, even if the new value is the same as the property’s current value. All this is quite murky to me, even if I have used it plenty of times within the course. Here’s an example of them in action:
class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
  • Global and Local Variables: global variables are variables that are defined outside of any function, method, closure, or type context. Local variables are variables that are defined within a function, method, or closure context.
    • Global constants and variables are always computed lazily, local constants and variables are never computed lazily.
  • Type Properties: instance properties are properties that belong to an instance of a particular type. We can also define properties that belong to the type itself, not to any one instance of that type. There will only ever be one copy of these properties, no matter how many instances of that type we create. These kinds of properties are called type properties.
    • Unlike stored instance properties, we must always give stored type properties a default value.
    • Type Property Syntax: we define type properties with the static keyword. For computed type properties for class types, we can use the class keyword instead to allow subclasses to override the superclass’s implementation. The example below shows the syntax for stored and computed type properties:
struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}
  • Querying and Setting Type Properties: type properties are queried and set on the type, not on an instance of that type. Here’s an example:
struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // cap the new audio level to the threshold level
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // store this as the new overall maximum input level
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}

Methods

Methods are functions that are associated with a particular type.

  • Instance Methods: instance methods are functions that belong to instances of a particular class, structure, or enumeration.
    • The self Property
    • Modifying Value Types from Within Instance Methods: structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.
    • Assigning to self within a Mutating Method

If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).

If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s books from this Affiliate Link.

Anyways, thank you so much for reading!

Till the next one!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: