Swift 5.9 introduced macros, parameter packs, and the if /
switch as expressions. Swift 5.10 tightens strict concurrency —
a single binary deploys across Apple platforms; Linux + Windows are first-class. Foundation has been re-implemented in
pure Swift for non-Apple targets (Open Source swift-corelibs-foundation). For Apple-platform UI,
SwiftUI is the modern path; UIKit / AppKit are still supported.
Install · SwiftPMSetup
bash
# macOS — Swift ships with the Xcode command-line tools
xcode-select --install
# Linux — install the official tarball from swift.org/install
# (or use swiftly, the toolchain manager)
curl -O https://download.swift.org/swift-5.10-release/ubuntu2204/...
tar xzf swift-*.tar.gz
# New SwiftPM project
swift package init --type executable --name MyApp
swift run # build + run
swift build -c release # optimized binary in .build/release/
# Daily commands
swift test # XCTest
swift package update # bump deps
swift-format --in-place --recursive Sources/
# Open the same package in Xcode
xed . # opens current dir in Xcode
Where things liveCommon imports
import Foundation
Dates, URLs, networking, JSON. Standard for any non-trivial program.
import SwiftUI
Declarative UI for Apple platforms.
import Combine
Reactive streams (mostly superseded by AsyncSequence).
import OSLog
Structured logging via Logger.
import SwiftData
Persistent stores via @Model macros.
import Observation
@Observable macro replaces ObservableObject.
import XCTest
Built-in unit testing.
@preconcurrency import Legacy
Suppress strict-concurrency warnings on an older import.
let · var · primitivesVariables & types
Bindings
let x = 5
Immutable. Default. Use whenever possible.
var y = 5
Mutable.
let x: Int = 5
Explicit type. Inferred otherwise.
let (a, b) = (1, 2)
Tuple destructuring.
let v: lazy Int = compute()
Legacy Old syntax. Modern is lazy var in a type.
typealias UserID = String
Type alias.
Built-in types
Int, Int8, Int16, Int32, Int64
Signed integers. Int is 64-bit on 64-bit platforms.
UInt, UInt8, UInt16, UInt32, UInt64
Unsigned variants. Use only at FFI boundaries.
Double, Float, Float16
Prefer Double. Float16 for ML interop.
Bool, String, Character
Character = grapheme cluster, not a code point.
Array<T> / [T]
Dynamic array. Value type with COW.
Dictionary<K, V> / [K: V]
Hash map.
Set<T>
Hash set.
(a: Int, b: String)
Tuple. Named fields are optional.
String interpolation: "x = \(x)"
Backslash-paren, not ${}.
"""raw"""
Multi-line string. Indentation is trimmed to the closing """.
The big ideaOptionals
var name: String? = "Ada"
Nullable. Sugar for Optional<String>.
if let name = name { … }
Bind when non-nil. Shadows the outer name.
guard let name else { return }
Preferred Early exit. Keeps happy path flat.
name ?? "anon"
Nil-coalescing. Right side is the default.
name?.uppercased()
Optional chaining. Short-circuits on nil.
name!
Force-unwrap. Crashes on nil. Avoid.
var name: String!
Implicitly-unwrapped optional. For lazy / DI props.
.flatMap { v in … }
Optional’s map that unwraps a returned optional.
Worked example
swift
// Optionals — every wrapped value must be unwrapped before use
let name: String? = "Ada"
let email: String? = nil
// 1 · if-let — bind only when non-nil
if let name = name {
print("name = \(name)")
}
// 2 · guard-let — early exit, keep happy path flat
func greet(_ name: String?) {
guard let name, !name.isEmpty else {
print("anon"); return
}
print("hi \(name)") // unwrapped from here on
}
// 3 · nil-coalescing — default when missing
let display = email ?? "no email"
// 4 · optional chaining — short-circuits on nil
let upper = email?.uppercased() // String?
// 5 · throwing variant — bridges Result/throws
func parsePort(_ s: String?) throws -> Int {
guard let s, let n = Int(s) else {
throw URLError(.badURL)
}
return n
}
print(display)
greet(nil)
greet("Grace")
print(try parsePort("8080"))
if · switch · forControl flow
if x > 0 { … } else { … }
Braces required. No parens.
if x > 0, let v = opt { … }
Multiple conditions, comma-separated.
let v = if x > 0 { "+" } else { "-" }
if as an expression (5.9+).
switch x { case …: … default: … }
Exhaustive. Each case auto-breaks.
case let .point(x, y): …
Pattern bind on associated values.
case 0..<10: …
Range matching.
case let p where p.x > 0: …
Where-clause guard.
for i in 0..<10
Half-open range.
for (i, v) in xs.enumerated()
Index + value.
while cond { … } / repeat { … } while cond
Pre / post test.
Labels · closuresFunctions & closures
func add(_ a: Int, _ b: Int) -> Int { a + b }
Single-expression body returns implicitly.
func send(to: String, subject: String = "hi")
Default args. Labels are public by default.
send(to: "x@y", subject: "z")
Call-site labels — this is why APIs read like sentences.
func first<T>(_ xs: [T]) -> T?
Generic.
func first<C: Collection>(_ c: C) -> C.Element?
Constraint via the type system.
func parse() throws -> Value
Throwing function. Callers use try.
func go() async throws -> T
Async + throwing. Combine freely.
let sum = { (a: Int, b: Int) in a + b }
Closure literal.
xs.filter { $0 > 0 }
$0, $1: implicit args.
@escaping closure
Required when a closure outlives the call (stored, async).
@Sendable closure
Safe to cross actors. Required for Task in strict concurrency.
Value · reference · taggedStructs, classes, enums
Structs (value types)
struct Point { var x: Int; var y: Int }
Memberwise init synthesized.
struct Point: Equatable, Hashable, Codable {}
Protocol conformance auto-synthesized.
extension Point { func mag() -> Double { … } }
Add methods without modifying the type.
mutating func scale(by k: Int)
Methods that mutate a struct must be marked.
Use struct by default
Value semantics + COW = simpler concurrency story.
Classes (reference types)
final class Service { … }
final prevents subclassing — almost always what you want.
class Sub: Base { override func … }
Single inheritance + protocols.
deinit { … }
Destructor. Runs on the last reference release.
weak var owner: Owner?
Break reference cycles. Optional by definition.
unowned let owner: Owner
Non-optional weak. Crashes if accessed after deallocation.
Enums
enum Direction { case north, south, east, west }
Plain enum.
enum Result { case success(T), failure(Error) }
Associated values per case — tagged union.
enum HTTP: Int { case ok = 200, notFound = 404 }
Raw values. HTTP(rawValue: 200) → Optional.
indirect enum Tree { case leaf(Int); case node(Tree, Tree) }
Recursive enums need indirect.
The polymorphism storyProtocols & generics
protocol Greeter { func hello() -> String }
Define a contract. Methods + props + associated types.
Pure Foundation + Codable. TaskGroup fans out three GitHub API requests, each individually wrapped in
Result so one failure doesn’t cancel the rest. No external dependencies.
swift
// Package.swift dependencies:
// .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"),
import Foundation
struct Repo: Decodable {
let name: String
let stargazersCount: Int
enum CodingKeys: String, CodingKey {
case name, stargazersCount = "stargazers_count"
}
}
func fetchRepos(owner: String) async throws -> [Repo] {
var req = URLRequest(url: URL(string: "https://api.github.com/users/\(owner)/repos?per_page=5")!)
req.setValue("devshelf", forHTTPHeaderField: "User-Agent")
let (data, resp) = try await URLSession.shared.data(for: req)
guard let http = resp as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode([Repo].self, from: data)
}
@main
struct Main {
static func main() async {
let owners = ["apple", "vapor", "pointfreeco"]
// Concurrent fan-out via async let
await withTaskGroup(of: (String, Result<[Repo], Error>).self) { group in
for owner in owners {
group.addTask {
do { return (owner, .success(try await fetchRepos(owner: owner))) }
catch { return (owner, .failure(error)) }
}
}
for await (owner, result) in group {
switch result {
case .success(let repos):
for r in repos { print("\(owner)/\(r.name) ★\(r.stargazersCount)") }
case .failure(let err):
print("\(owner): \(err.localizedDescription)")
}
}
}
}
}
Best practiceGood to know
Use structs by default; reach for classes only when you need identity.
Value types make local reasoning easy — no shared mutable state, no surprise aliasing. Classes are right for things with an inherent identity (a network session, a controller).
Mark types final unless they need to be subclassed.
Final classes get devirtualized by the optimizer and lock down your API surface. Inherited classes are usually a refactor smell.
Adopt strict concurrency early in a project.
The compiler flags unsafe sharing of mutable state at compile time. Turning it on later means fixing dozens of warnings at once.
Common trapsWatch out for
! is force-unwrap, not assertion.
It crashes the process on nil. Tempting in the moment, painful in production logs. Use guard let or ?? instead.
Closures capture by reference, not by value.
A [weak self] capture list is mandatory anywhere a closure outlives self — especially in long-running async tasks and Combine subscriptions.
Calling synchronous blocking work inside an actor stalls every other caller.
Actors serialize. A heavy disk read or sync HTTP call blocks every other message. Move CPU/IO work off-actor via Task.detached or async APIs.
An optional is a type that can hold either a value or nil. Declare with a ? suffix: var name: String? = nil. Unwrap safely with if let, guard let, or the nil-coalescing operator ??. Forced unwrapping with ! crashes at runtime if nil, so use it only when you have a logical guarantee the value exists. Optionals eliminate null-pointer exceptions by making nil-ability explicit in the type system.
What is the difference between struct and class in Swift?
Structs are value types: each assignment or function pass creates a copy. Classes are reference types: multiple variables can refer to the same instance. Swift prefers structs for model data (they are thread-safe by default and benefit from copy-on-write optimisation) and classes for objects that need identity, inheritance, or Objective-C interoperability like UIViewController subclasses.
What are Swift protocols?
A protocol defines a blueprint of methods, properties, and requirements that a conforming type must implement. Swift uses protocols for polymorphism instead of class inheritance: a struct, enum, or class can conform to multiple protocols simultaneously. Protocol extensions let you provide default implementations, turning a protocol into a powerful mixin pattern without runtime overhead.
How does async/await work in Swift?
Mark a function async to allow it to suspend without blocking a thread: func fetchData() async throws -> Data. Call it with await fetchData(). The Swift concurrency runtime suspends the current task at await points and resumes it when the result is ready, freeing the thread for other work. Use async let for parallel execution and Task {} to call async code from synchronous contexts.
What are Swift actors?
An actor is a reference type that serialises access to its mutable state, preventing data races without manual locking. Declare with actor MyActor { var count = 0 }. Access to actor-isolated properties and methods requires await, which signals that the call may need to wait for the actor to become available. Use @MainActor to pin UI-updating code to the main thread automatically.