Swift 5.3 - Section 1: Swift Basics

Swift 5.3 - Section 1: Swift Basics

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 1: Expressions, Variables & Constants

Code comments

Swift allows single line comments, multi line comments and also nested comments:

// This is a comment. It is not executed.

/* This is also a comment.
   Over many..
   many...
   many lines. */

Nested comments are supported:

/* This is a comment.
   /* And inside it
      is
      another comment.
   */
   
   Back to the first.
*/

Print in the debug area

print("Hello, Swift Apprentice reader!")

How to remove the new-line at the end, or how to finish the current line:

print("\(player), ", terminator: "") // no newline

print("") // print one final newline

The remainder operation

Between ints:

28 % 10

Between decimal numbers:

(28.0).truncatingRemainder(dividingBy: 10.0)

Some math functions

max(5, 10)
// 10

min(-5, -10)
// -10

sin(45 * Double.pi / 180)
// 0.7071067811865475

cos(135 * Double.pi / 180)
// -0.7071067811865475

(2.0).squareRoot()
// 1.414213562373095

Int.random(in: 1...6)

Constants and variables

let number: Int = 10
var variableNumber: Int = 42

Variables are block-scoped.

Chapter 2: Types & Operations

Type conversion

var integer: Int = 100
var decimal: Double = 12.5
integer = Int(decimal)

Type inference

In Xcode, you can analyze the type of a variable or constant with Option+Click on the name itself.

How to cast with type inference:

let actuallyDouble: Double = 3
let actuallyDouble: Double(3)
let actuallyDouble = 3 as Double

Characters and strings

let characterA: Character = "a"
let stringDog = "Dog" // Inferred to be of type String

Interpolation:

message = "Hello my name is \(name)!" // "Hello my name is Matt!"

Multi-line strings:

let bigString = """
  You can have a string
  that contains multiple
  lines
  by
  doing this.
  """
print(bigString)

Tuples

let coordinates: (Int, Int) = (2, 3)
let x1 = coordinates.0
let y1 = coordinates.1

Named tuples:

let coordinatesNamed = (x: 2, y: 3)
// Inferred to be of type (x: Int, y: Int)
let x2 = coordinatesNamed.x
let y2 = coordinatesNamed.y

Deconstruction:

let coordinates3D = (x: 2, y: 3, z: 1)
let (x3, y3, z3) = coordinates3D
let (x4, y4, _) = coordinates3D

Type aliases

typealias Animal = String
let myPet: Animal = "Dog"

typealias Coordinates = (Int, Int)
let xy: Coordinates = (2, 4)

Chapter 3: Basic Control Flow

Bool toggling

var switchState = true
switchState.toggle() // switchState = false
switchState.toggle() // switchState = true

If statement

let hourOfDay = 12
var timeOfDay = ""

if hourOfDay < 6 {
  timeOfDay = "Early morning"
} else if hourOfDay < 12 {
  timeOfDay = "Morning"
} else if hourOfDay < 17 {
  timeOfDay = "Afternoon"
} else if hourOfDay < 20 {
  timeOfDay = "Evening"
} else if hourOfDay < 24 {
  timeOfDay = "Late evening"
} else {
  timeOfDay = "INVALID HOUR!"
}
print(timeOfDay)

AND and OR conditions are short-circuited.

While loops

var sum = 1

while sum < 1000 {
  sum = sum + (sum + 1)
}

Repeat While loops

sum = 1

repeat {
  sum = sum + (sum + 1)
} while sum < 1000

Chapter 4: Advanced Control Flow

Countable ranges

let closedRange = 0...5
let halfOpenRange = 0..<5

For loops

let count = 10
var sum = 0
for i in 1...count {
  sum += i
}

Conditions in loops:

sum = 0
for i in 1...count where i % 2 == 1 {
  sum += i
}

Continue to an outer loop

sum = 0

rowLoop: for row in 0..<8 {
  columnLoop: for column in 0..<8 {
    if row == column {
      continue rowLoop
    }
    sum += row * column
  }
}

Switch statements

switch hourOfDay {
  case 0...5:
    timeOfDay = "Early morning"
  case 6...11:
    timeOfDay = "Morning"
  case 12...16:
    timeOfDay = "Afternoon"
  case 17...19:
    timeOfDay = "Evening"
  case 20..<24:
    timeOfDay = "Late evening"
  default: timeOfDay = "INVALID HOUR!"
}

Condition on cases:

switch number {
  case let x where x % 2 == 0:
    print("Even")
  default:
    print("Odd")
}

Pattern matching:

let coordinates = (x: 3, y: 2, z: 5)

switch coordinates {
  case (0, 0, 0): // 1
    print("Origin")
  case (_, 0, 0): // 2
    print("On the x-axis.")
  case (0, _, 0): // 3
    print("On the y-axis.")
  case (0, 0, _): // 4
    print("On the z-axis.")
  default: // 5
    print("Somewhere in space")
}

Pattern matching when capturing variables:

switch coordinates {
  case (0, 0, 0):
    print("Origin")
  case (let x, 0, 0):
    print("On the x-axis at x = \(x)")
  case (0, let y, 0):
    print("On the y-axis at y = \(y)")
  case (0, 0, let z):
    print("On the z-axis at z = \(z)")
  case let (x, y, z):
    print("Somewhere in space at x = \(x), y = \(y), z = \(z)")
}

More complex example:

switch coordinates {
  case let (x, y, _) where y == x:
    print("Along the y = x line.")
  case let (x, y, _) where y == x * x:
    print("Along the y = x^2 line.")
  default:
    break
}

Chapter 5: Functions

External names

Renaming an external name of a parameter:

func printMultipleOf(multiplier: Int, and value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}

printMultipleOf(multiplier: 4, and: 2)

Hiding an external name of a parameter:

func printMultipleOf(_ multiplier: Int, and value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}

printMultipleOf(4, and: 2)

Functions support default values.

Return values

Return values:

func multiply(_ number: Int, by multiplier: Int) -> Int {
  return number * multiplier
}

let result = multiply(4, by: 2)

Return value with a tuple:

func multiplyAndDivide(_ number: Int, by factor: Int)
                   -> (product: Int, quotient: Int) {
  return (number * factor, number / factor)
}

let results = multiplyAndDivide(4, by: 2)
let product = results.product
let quotient = results.quotient

Removing the return value for single statement functions:

func multiply(_ number: Int, by multiplier: Int) -> Int {
  number * multiplier
}

func multiplyAndDivide(_ number: Int, by factor: Int)
                   -> (product: Int, quotient: Int) {
  (number * factor, number / factor)
}

Variadic parameters

func getHighestGrade(for grades: Int...) -> Int {
  grades.max() ?? 0
}

getHighestGrade()           // 0
getHighestGrade(3, 7, 5)    // 7

Parameters passed by reference

func incrementAndPrint(_ value: inout Int) {
  value += 1
  print(value)
}

var count = 0
incrementAndPrint(&count)

Overloading

func printMultipleOf(multiplier: Int, andValue: Int)
func printMultipleOf(multiplier: Int, and value: Int)
func printMultipleOf(_ multiplier: Int, and value: Int)
func printMultipleOf(_ multiplier: Int, _ value: Int)

Functions as variables

Note that when passing a function as variable, the parameter names are not considered in the function signature.

func add(a: Int, b: Int) -> Int {
  a + b
}

var function = add

function(4, 2)

Passing a function as a parameter to another function:

func printResult(_ function: (Int, Int) -> Int, _ a: Int, _ b: Int) {
  let result = function(a, b)
  print(result)
}

printResult(add, 4, 2)

No return

func noReturn() -> Never {
}

Commenting your functions

You can automatically comment a function in Xcode with Option-Command-/.

Sample of function declaration:

/// Calculates the average of three values
/// - Parameters:
/// - a: The first value.
/// - b: The second value.
/// - c: The third value.
/// - Returns: The average of the three values.
func calculateAverage(of a: Double, and b: Double, and c: Double) -> Double {
  let total = a + b + c
  let average = total / 3
  return average
}
calculateAverage(of: 1, and: 3, and: 5)

Closures

A closure is an anonymous method without parameter names.
A closure always require the return type, even if it is Void.

typealias Operate = (Int, Int) -> Int
let op: Operate = +

var addClosure: Operate = { (a: Int, b: Int) -> Int in
  return a + b
}

Shortening closure syntax:

let longClosure = { (a: Int, b: Int) -> Int in
  a * b
}

let noParameterTypes: Operate = { (a, b) -> Int in
  a * b
}

let noReturnType: Operate = { (a, b) in
  a * b
}

let shortClosure: Operate = { $0 * $1 }

Closures with Void return type:

let voidClosure: () -> Void = { () -> Void in
  print("Test")
}

let voidClosure: () -> Void = {
  print("Test")
}

Chapter 6: Optionals

Optionals

var errorCode: Int?
errorCode = 100
errorCode = nil

Unwrapping

Force unwrapping:

var authorName: String? = "Matt Galloway"
var unwrappedAuthorName = authorName!
print("Author is \(unwrappedAuthorName)")

Optional binding (plus shadowing, because the unwrapped variable name is the same as the optional name):

if let authorName = authorName {
  print("Author is \(authorName)")
} else {
  print("No author.")
}

Multiple optional bindings (all the optionals must be not nil to enter the if statement):

if let authorName = authorName,
   let authorAge = authorAge {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age.")
}

Combine multiple unwrapping with additional boolean checks:

if let authorName = authorName,
   let authorAge = authorAge,
   authorAge >= 40 {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age or age less than 40.")
}

Introducing guard

Check of input parameters of functions (with guard, you have always to provide an else clause):

func guardMyCastle(name: String?) {
  guard let castleName = name else {
    print("No castle!")
    return
  }

  // At this point, `castleName` is a non-optional String
  print("Your castle called \(castleName) was guarded!")
}

Example of another use:

func maybePrintSides(shape: String) {
  guard let sides = calculateNumberOfSides(shape: shape) else {
    print("I don’t know the number of sides for \(shape).")
    return
  }
  
  print("A \(shape) has \(sides) sides.")
}

Nil coalescing

var optionalInt: Int? = 10
var mustHaveResult = optionalInt ?? 0