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