Swift

Mar 25th, 2022

References

Decimals

  • When you create a floating-point number, Swift considers it to be a Double. That’s short for “double-precision floating-point number”.
  • Swift considers decimals to be a wholly different type of data to integers, which means you can’t mix them together. After all, integers are always 100% accurate, whereas decimals are not, so Swift won’t let you put the two of them together unless you specifically ask for it to happen.
  • Swift decides whether you wanted to create a Double or an Int based on the number you provide

Older APIs use CGFloat. If you see CGFloat you can just ignore it and use Double.

Collection Types

Swift provides three primary collection types, known as arrays, sets, and dictionaries, for storing collections of values. Arrays are ordered collections of values. Sets are unordered collections of unique values. Dictionaries are unordered collections of key-value associations.

Arrays

An array stores values of the same type in an ordered list. The same value can appear in an array multiple times at different positions.

Sets

A set stores distinct values of the same type in a collection with no defined ordering. You can use a set instead of an array when the order of items isn’t important, or when you need to ensure that an item only appears once.

var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// Prints "letters is of type Set<Character> with 0 items."

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items

for genre in favoriteGenres {
    print("\(genre)")
}
// Classical
// Jazz
// Hip hop

Dictionaries

Dictionary is very similar to Java Map. It cannot contain duplicate keys: Each key can map to at most one value.

let person = [
    "name": "John Doe",
    "title": "Mr", 
    "location": "US"
]
let hasGraduated = [
    "Eric": false,
    "Maeve": true,
    "Otis": false,
]
let olympics = [
    2012: "London",
    2016: "Rio de Janeiro",
    2021: "Tokyo"
]

You can also create an empty dictionary using whatever explicit types you want to store, then set keys one by one:

var heights = [String: Int]()
heights["Yao Ming"] = 229
heights["Shaquille O'Neal"] = 216
heights["LeBron James"] = 206

Type annotations

Type annotations let us be explicit about what data types we want.

let surname: String = "Lasso" // Sttring
var score: Int = 0 // Int
let pi: Double = 3.141 // Double
var isAuthenticated: Bool = true // Bool
var albums: [String] = ["Red", "Fearless"] // Array
var user: [String: String] = ["id": "@twostraws"] // Dictionary
var books: Set<String> = Set(["The Bluest Eye", "Foundation", "Girl, Woman, Other"]) // Set
var teams: [String] = [String]() // Empty array of strings
var cities: [String] = [] // Empty array of strings
var clues = [String]() // Empty array of strings

Conditions & Loops

Loops

// Loop an array of strings
let platforms = ["iOS", "macOS", "tvOS", "watchOS"]
for os in platforms {
    print("Swift works great on \(os).")
}

// Loop between [1,12]
for i in 1...12 {
    print("5 x \(i) is \(5 * i)")
}

// Loop between [1,5)
for i in 1..<5 {
    print("Counting 1 up to 5: \(i)")
}

// Loop without loop variable
var lyric = "Haters gonna"
for _ in 1...5 {
    lyric += " hate"
}

// While loop
var countdown = 5
while countdown > 0 {
    print("\(countdown)…")
    countdown -= 1
}

// Repeat-While loop
repeat {
    statements
} while condition

If you call continue inside the loop body, Swift will immediately stop executing the current loop iteration and jump to the next item in the loop.

If you call break inside the loop body, Swift will exit the current loop immediately and skip all remaining iterations.

Functions

Return values

// Returns a single value as Bool
func isUppercase(string: String) -> Bool {
    string == string.uppercased()
}

In order to return multiple values from a function, an array, set or dictionary can be used as return type.

// Returns a single value as Array
func getUser() -> [String] {
    ["Taylor", "Swift"]
}

// Returns a single value as Dictionary
func getUser() -> [String: String] {
    [
        "firstName": "Taylor",
        "lastName": "Swift"
    ]
}

It is recommended to use Tuple to return multiple values. Tuple lets us put multiple pieces of data into a single variable. They have a fixed size and can have a variety of data types.

func getUser() -> (firstName: String, lastName: String) {
    (firstName: "Taylor", lastName: "Swift")
}
let user = getUser()
print("Name: \(user.firstName) \(user.lastName)")

func getUser() -> (String, String) {
    ("Taylor", "Swift")
}
let user = getUser()
print("Name: \(user.0) \(user.1)")

We can pull apart the return value from the function into two separate constants.

let (firstName, lastName) = getUser()
print("Name: \(firstName) \(lastName)")

If you don’t need all the values from the tuple you can go a step further by using _ to tell Swift to ignore that part of the tuple:

let (firstName, _) = getUser()
print("Name: \(firstName)")

Customizing parameter labels

Adding an underscore before the parameter name, it will remove the external parameter label.

func isUppercase(_ string: String) -> Bool {
    string == string.uppercased()
}
let string = "HELLO, WORLD"
let result = isUppercase(string: string) // without underscore
let result = isUppercase(string) // with underscore, it is no need to write an external parameter name

Another problem in parameters is reserved keywords that you cannot use the keywords as parameter name.

func printTimesTables(for: Int) {
    for i in 1...12 {
        print("\(i) x \(for) is \(i * for)") // throws error since `for` is not allowed inside the function
    }
}
printTimesTables(for: 5)

func printTimesTables(for number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}
printTimesTables(for: 5) // So, now you can use `for` as second parameter name

Closures

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to lambdas in Java and other programming languages.

Closures can capture and store references to any constants and variables from the context in which they’re defined. This is known as closing over those constants and variables.

// Closure Expression Syntax
{ (parameters) -> return type in
    statements
}
let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }
    return name1 < name2
})

Swift can automatically provide parameter names for us, using shorthand syntax. Swift provides for us: $0 and $1, for the first and second string parameters respectively.

let reverseTeam = team.sorted { $0 > $1 }

Functions as parameters

Having three trailing closures is not as uncommon as you might expect. To demonstrate this here’s a function that accepts three function parameters, each of which accept no parameters and return nothing:

func doImportantWork(first: () -> Void, second: () -> Void, third: () -> Void) {
    print("About to start first work")
    first()
    print("About to start second work")
    second()
    print("About to start third work")
    third()
    print("Done!")
}

doImportantWork {
    print("This is the first work")
} second: {
    print("This is the second work")
} third: {
    print("This is the third work")
}

Structures and Classes

Swift doesn’t require you to create separate interface and implementation files for custom structures and classes.

Structures and classes in Swift have many things in common. Both can:

  • Define properties to store values
  • Define methods to provide functionality
  • Define subscripts to provide access to their values using subscript syntax
  • Define initializers to set up their initial state
  • Be extended to expand their functionality beyond a default implementation
  • Conform to protocols to provide standard functionality of a certain kind

Classes have additional capabilities that structures don’t have:

  • Inheritance enables one class to inherit the characteristics of another.
  • Type casting enables you to check and interpret the type of a class instance at runtime.
  • Deinitializers enable an instance of a class to free up any resources it has assigned.
  • Reference counting allows more than one reference to a class instance.

Dynamic property values

struct Employee {
    let name: String
    var vacationAllocated = 14
    var vacationTaken = 0

    // only getter
    var vacationRemaining: Int {
        vacationAllocated - vacationTaken
    }

    // with getter and setter
    var vacationRemaining: Int {
        get {
            vacationAllocated - vacationTaken
        }

        set {
            vacationAllocated = vacationTaken + newValue
        }
    }
}

Take action when a property changes

In Swift, there are property observers that run when properties change. A didSet observer to run when the property just changed, and a willSet observer to run before the property changed.

struct Game {
    var score = 0 {
        didSet {
            print("Score is now \(score)")
        }
    }
}

Swift automatically provides the constant oldValue inside didSet and the constant newValue inside willSet.

struct App {
    var contacts = [String]() {
        willSet {
            print("Current value is: \(contacts)")
            print("New value will be: \(newValue)")
        }

        didSet {
            print("There are now \(contacts.count) contacts.")
            print("Old value was \(oldValue)")
        }
    }
}

var app = App()
app.contacts.append("Adrian E")
app.contacts.append("Allen W")
app.contacts.append("Ish S")

Initializers

Swift silently generates the initializer for us based on the properties we place inside a struct. You can still create a custom one yourself.

Swift won’t automatically generate a memberwise initializer for classes. This means you either need to write your own initializer, or assign default values to all your properties.

Inheritance

class Employee {
    let hours: Int

    init(hours: Int) {
        self.hours = hours
    }

    func printSummary() {
        print("I work \(hours) hours a day.")
    }
}

class Developer: Employee {
    let isPrincipal: Bool

    init(hours: Int, isPrincipal: Bool) {
        self.isPrincipal = isPrincipal
        super.init(hours: hours)
    }

    func work() {
        print("I'm writing code for \(hours) hours.")
    }

    override func printSummary() {
        print("I'm a developer who will sometimes work \(hours) a day, but other times spend hours arguing about whether code should be indented using tabs or spaces.")
    }
}

class Manager: Employee {
    func work() {
        print("I'm going to meetings for \(hours) hours.")
    }
}

Copy classes and structures

class User {
    var username = "Anonymous"
}
var user1 = User()
var user2 = user1
user2.username = "Taylor"
print(user1.username) // Taylor
print(user2.username) // Taylor

Structs do not share their data amongst copies, meaning that if we change class User to struct User in our code we get a different result.

struct User {
    var username = "Anonymous"
}
var user1 = User()
var user2 = user1
user2.username = "Taylor"
print(user1.username) // Anonymous
print(user2.username) // Taylor

If you want to create a unique copy of a class instance – sometimes called a deep copy – you need to handle creating a new instance and copy across all your data safely.

class User {
    var username = "Anonymous"

    func copy() -> User {
        let user = User()
        user.username = username
        return user
    }
}

Extensions

Extensions add new functionality to an existing class, structure, enumeration, or protocol type. An extension can extend an existing type to make it adopt one or more protocols.

extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

Example: You can extend the Rect structure to provide an additional initializer that takes a specific center point and size:

extension Rect {
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

Protocols

Swift protocols are very similar to Java interfaces.

protocol SomeProtocol {
    init(someParameter: Int) // Initializer Requirements
    var mustBeSettable: Int { get set } // Property Requirements
    var doesNotNeedToBeSettable: Int { get } // Property Requirements
    static func someTypeMethod() // Method Requirements
}
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set } // Property Requirements
    func random() -> Double // Method Requirements
    mutating func toggle() // Mutating Method Requirements
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}
class SomeClass: SomeProtocol, FirstProtocol, AnotherProtocol {
    // The following is due to Initializer Requirements in `SomeProtocol`
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
    // class definition goes here
}

Comparable

Comparable is a Swift protocol, which allows Swift to see if one object should be sorted before another. Custom types cannot be implemented automatically by Swift. It is needed to write a function called < that accepts two instances of your struct as its parameter, and returns true if the first instance should be sorted before the second.

struct Location: Equatable, Comparable {
    let name: String
}

func <(lhs: Location, rhs: Location) -> Bool {
    lhs.name < rhs.name
}

Optionals

Unwrapping optionals

By using variable shadowing, we are temporarily creating a second constant of the same name, available only inside the condition’s body. It is very common and mainly used with optional unwraps.

with if let, it lets us run if number has a value inside.

var number: Int? = nil
if let number = number {
    // Run if number has a value inside
    print(function1(number: number))
}

with guard let, it lets us run if number doesn’t have a value inside.

func printSquare(of number: Int?) {
    // Run if number doesn't have a value inside
    guard let number = number else {
        print("Missing input")
        return
    }
    print("\(number) x \(number) is \(number * number)")
}

with nil coalescing operator, it lets us provide a default value if the optional was empty.

let captains = [
    "Enterprise": "Picard",
    "Voyager": "Janeway",
    "Defiant": "Sisko"
]
let new = captains["Serenity"] ?? "N/A"
let new = captains["Serenity", default: "N/A"]

Optional chaining

struct Book {
    let title: String
    let book: String?
}

var book: Book? = nil
let author = book?.author?.first?.uppercased() ?? "A"