Mobile development: iOS

Sefism.

Chapter 01

Swift essentials

The Swift 6 that SwiftUI code relies on: optionals, value types, enums, closures, protocols, errors and structured concurrency with actors.

let, var and type inference

Also called type inference

What
let declares a constant and var a variable. Swift infers the type from the initial value, and every value has a fixed type.
Why
Prefer let: the compiler warns when a var is never changed, and constants make value types safe to share.
Swift
let name = "Ana"
var score = 0
score += 10
let scores: [String: Int] = ["ana": 90, "li": 72]
let passed = scores.filter { $0.value >= 75 }.map(\.key)

Optionals and unwrapping

Also called if let, guard let, optional, force unwrap, optional chaining

What
An optional String? either holds a value or nil. You unwrap it safely with if let, guard let, optional chaining ?. or a default with ??.
Why
Swift will not let you use a possibly missing value by accident, which removes a whole class of crashes.
Swift
var nickname: String? = nil

let shown = nickname ?? "Guest"
if let nickname {
  print("Hi, \(nickname)")
}

func greet(_ name: String?) {
  guard let name else { return }
  print("Hello, \(name)")
}

Watch out

Force unwrapping with ! crashes when the value is nil; reserve it for values that truly cannot be missing.

Structs versus classes

Also called value types, reference types, copy-on-write

What
Structs are value types: assigning one makes an independent copy. Classes are reference types: several variables can point at the same object.
Why
SwiftUI views and most models are structs, which makes state changes predictable. Use a class when you need shared identity, such as an @Observable model many views read.
Swift
struct Point { var x = 0 }
var a = Point()
var b = a
b.x = 5
print(a.x) // 0: b is a copy

final class Box { var x = 0 }
let c = Box()
let d = c
d.x = 5
print(c.x) // 5: c and d share one object

Enums with associated values

Also called associated value

What
Swift enums can carry data with each case, such as .loaded([Post]). A switch over an enum must cover every case.
Why
Screen state, API results and navigation routes are modelled as enums, so the compiler stops you forgetting the error or empty case.
Swift
enum LoadState {
  case loading
  case loaded([Post])
  case failed(any Error)
}

switch state {
case .loading: ProgressView()
case .loaded(let posts): PostList(posts: posts)
case .failed(let error): Text(error.localizedDescription)
}

Closures and capture lists

Also called [weak self], trailing closure, capture list, escaping closure, retain cycle

What
Closures are functions without a name that capture surrounding values. A trailing closure goes after the parentheses. A capture list like [weak self] controls how a class instance is captured.
Why
SwiftUI actions, sorting and completion handlers are closures, and capture lists prevent retain cycles that leak memory.
Swift
let sorted = names.sorted { $0 < $1 }

final class ProfileModel {
  var onSave: (() -> Void)?
  func bind(to button: SaveButton) {
    button.action = { [weak self] in
      self?.save()
    }
  }
  func save() {}
}

Watch out

A class that stores a closure which strongly captures self never gets freed.

Protocols and extensions

Also called protocol, protocol extension

What
A protocol lists requirements a type promises to meet, like Identifiable or Codable. An extension adds methods, including default implementations, to an existing type or protocol.
Why
SwiftUI is built on protocols (View, Hashable), and protocols let you swap a real network client for a fake in tests.
Swift
protocol Shape {
  var area: Double { get }
}

struct Circle: Shape {
  let radius: Double
  var area: Double { .pi * radius * radius }
}

extension Shape {
  func describe() -> String { "Area \(area)" }
}

Error handling with throws

Also called do try catch, typed throws

What
A function marked throws can fail; callers use try inside do and catch. Swift 6 also allows typed throws, such as throws(LoginError), when the error type is fixed.
Why
Network, decoding and file work all throw, and handling errors explicitly is what turns a failure into a useful message instead of a silent blank screen.
Swift
enum LoginError: Error { case badPassword, locked }
func login(_ pass: String) throws(LoginError) -> User {
  guard pass.count >= 8 else { throw .badPassword }
  return User()
}

do {
  let user = try login(input)
  show(user)
} catch {
  showMessage(for: error)
}

Watch out

Using try? everywhere throws the error away, so you never learn why something failed.

async/await and Task

Also called async let, structured concurrency, Swift concurrency

What
An async function can suspend while waiting. await marks each suspension point, async let runs work in parallel, and Task starts async work from synchronous code.
Why
Networking and databases are async. In SwiftUI the .task modifier starts work when a view appears and cancels it when the view goes away.
Swift
func loadDashboard() async throws -> Dashboard {
  async let user = api.user()
  async let feed = api.feed()
  return try await Dashboard(user: user, feed: feed)
}

Task {
  do { dashboard = try await loadDashboard() }
  catch { errorMessage = error.localizedDescription }
}

Actors, MainActor and Sendable

Also called data race safety, strict concurrency, main actor, data race, actor

What
An actor protects its state so only one task touches it at a time. @MainActor runs code on the main thread, and Sendable marks types safe to pass between tasks. Swift 6 checks all of this at compile time.
Why
Data races cause random crashes that are nearly impossible to reproduce. Swift 6 language mode turns them into compile errors, and UI code must run on the main actor.
Swift
actor ImageCache {
  private var images: [URL: Data] = [:]
  func image(for url: URL) -> Data? { images[url] }
  func store(_ data: Data, for url: URL) { images[url] = data }
}

@MainActor
@Observable
final class FeedModel {
  var posts: [Post] = []
}

Watch out

Silencing concurrency errors with @unchecked Sendable or nonisolated(unsafe) hides real races instead of fixing them.

Swift Package Manager

Also called SPM, SwiftPM

What
Apple's dependency manager, built into Xcode. You add a package by its Git URL and a version rule, and Xcode resolves and builds it with your app.
Why
It is the standard way to add libraries on iOS today, and splitting your own code into local packages speeds up builds and previews.
Example
In Xcode choose File, Add Package Dependencies, paste the repository URL, pick "Up to Next Major Version" and add the library to your app target. The resolved versions are saved in Package.resolved, which you commit.

Watch out

Not committing Package.resolved, so teammates and CI build with different library versions.

Swift collections

Also called Array, Dictionary, Set, map, compactMap

What
Array, Dictionary and Set are value types: assigning one copies it, cheaply, with copy on write. Functions such as map, filter, reduce and compactMap transform them.
Why
Lists of models feed every SwiftUI screen, and value semantics mean a copied array never changes behind your back.
Swift
var titles = ["Buy milk", "Call Sara"]
var copy = titles
copy.append("Gym")        // titles still has 2 items

let lengths = titles.map { $0.count }
let ids = ["1", "x", "3"].compactMap { Int($0) } // [1, 3]
var byId: [Int: String] = [:]
byId[1] = "Buy milk"
let done: Set<Int> = [1, 3]

Watch out

Force-unwrapping a dictionary lookup with !, which crashes when the key is missing.

01 · Swift essentials1 / 8