Open-source libraries used

1 libraries are bundled into this tool's code.

Swift Cheatsheet — Concise Reference

A cheatsheet for Swift 5.9 syntax, value types, protocols, and the most-used standard library — covering about 80% of everyday scenarios.

Sw

Swift Swift 5.9

LLVM (swiftc / Swift toolchain) · Multi-paradigm (Protocol-oriented · OO · Functional) · Static · Strongly typed

Recommended Learning Path

Start with print and variable bindings → master value types (struct/enum) and Optional → dive into functions, closures, and collections → understand protocol-oriented design and extensions → use do-catch and throws for error handling → write concurrency with async/await and DispatchQueue → then learn URLSession, dates, and build tests as needed. The FAQ section is great for revisiting pitfalls.

1.Hello World & Build Environment

Run Swift programs, SwiftPM projects and the toolchain.

Minimal program

Top-level code is the program entry point. `print` outputs. `import` brings in modules.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Create a new file main.swift:
import Foundation
print("Hello, world!")
// Run:
// swift main.swift
// Compile and run:
// swiftc main.swift -o app
// ./app
// Key points:
// Top-level statements execute directly
// No main function needed (script mode)
// print automatically appends a newline
// import Foundation is commonly used

Run & build

`swift` runs scripts directly. `swiftc` compiles. `swift run` runs a package.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Run a script directly:
// swift script.swift
// Compile to an executable:
// swiftc main.swift -o app
// ./app
// Run a SwiftPM package:
// swift run
// Specify the executable target:
// swift run MyTool
// Compile with optimization:
// swiftc -O main.swift
// Compile all source files:
// swiftc *.swift
// With debug info:
// swiftc -g main.swift
// Check the version:
// swift --version

SwiftPM project

`swift package init` initializes. `Package.swift` is the manifest. `Sources` is the source directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Create an executable package:
// swift package init --type executable
// Create a library package:
// swift package init --type library
// Structure:
// Package.swift manifest
// Sources/ source code
// MyTool/ target directory
// Tests/ tests
// Package.swift:
// swift-tools-version:5.9
// import PackageDescription
// let package = Package(
// name: "MyTool",
// targets: [.executableTarget(
// name: "MyTool")]
// )
// Build: swift build
// Run: swift run
// Test: swift test

`import`

`import` imports a module. `Foundation` is commonly used. Import submodules on demand.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Import standard modules:
import Foundation
import UIKit // iOS UI
import SwiftUI // SwiftUI UI
// Foundation provides:
// strings, dates, files, JSON
// numeric and collection enhancements
// Submodules:
import Foundation.NSURL
// Conditional import across platforms:
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// Import your own module's library:
// import MyLibrary
// import statements at the top of the file

Output & interpolation

`print` / `print(items:)`. String interpolation `\(value)`. Separators and terminators.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let name = "Nick"
let age = 30
// String interpolation:
print("\(name) is \(age) years old")
// Multi-value output:
print(1, 2, 3) // space-separated
print("a", "b", separator: "-") // a-b
print("x", terminator: "") // no newline
// Debug output:
debugPrint(name)
// Format control:
let pi = 3.14159
print(String(format: "%.2f", pi)) // 3.14
// Print arrays/dicts directly:
print([1, 2, 3])
print(["a": 1])
// Custom types implement CustomStringConvertible

Command-line arguments

`CommandLine.arguments` retrieves the arguments. The first is the program path.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Foundation
// All arguments:
let args = CommandLine.arguments
print("Total \(args.count) arguments")
for (i, a) in args.enumerated() {
print("\(i): \(a)")
}
// Business arguments:
// args[0] is the program path
// Starting from args[1] are the arguments
if args.count > 1 {
let name = args[1]
print("Hello \(name)")
}
// Simple parsing:
// Use ArgumentParser for complex arguments
import ArgumentParser // requires dependency
// Exit:
// exit(0) / exit(1)

Multiple files

Multiple source files in the same package share types. `internal` access level.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Sources/App/main.swift
let greeter = Greeter(name: "Swift")
greeter.greet()
// Sources/App/Greeter.swift
// Types within the same package are automatically visible:
struct Greeter {
let name: String
func greet() {
print("Hello, \(name)!")
}
}
// Access levels:
// private within the file
// internal within the module (default)
// public outside the module
// Compile:
// swiftc main.swift Greeter.swift
// SwiftPM automatically includes Sources/
// Filename doesn't matter, type name just needs to be unique

Xcode integration

Xcode project organization. targets and schemes. iOS development workflow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Project structure:
// MyApp.xcodeproj
// Sources/
// Resources/
// Tests/
// target: build unit
// iOS App / watchOS App
// Framework / Unit Test
// scheme: build + run combination
// Build from the command line:
// xcodebuild -scheme MyApp build
// Run tests:
// xcodebuild test -scheme MyApp
// Simulator:
// xcrun simctl list devices
// Export IPA:
// xcodebuild archive
// SwiftPM packages can be dragged in directly
// Difference from SwiftPM:
// Xcode handles signing and devices

2.Variables & constants

`var` / `let` bindings, type inference, scope, and naming.

`var` & `let`

`let` declares a constant; immutable. `var` declares a variable; mutable. Prefer `let`.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Constants:
let maxCount = 100
// maxCount = 200 // Error: immutable
// Variables:
var score = 0
score = 10 // modification allowed
// Type inference:
let name = "Swift" // String
let number = 42 // Int
// Explicit types:
let count: Int = 5
var price: Double = 3.5
// Naming principles:
// Use let whenever possible
// Use var only when modification is needed
// Immutability is safer in concurrent environments
// Use var for mutable collections, let for read-only

Type inference

The compiler infers types from literals and context. Annotate explicitly in complex cases.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Literal inference:
let a = 42 // Int
let b = 3.14 // Double
let c = "hi" // String
let d = true // Bool
// Contextual inference:
var array: [Int] = []
array.append(1) // element inferred as Int
// Literals can match multiple types:
let i: Int = 42
let f: Double = 42 // allowed
let u: UInt8 = 42
// When to annotate explicitly:
// generics, optionals, complex expressions
// Add annotations when the compiler complains
// Type alias:
typealias Age = Int
let age: Age = 30
// Shortcut: Option+Click to view the type

Explicit types

Annotate the type after a colon. Improves readability. Plays nicely with conversions and literals.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Basic annotations:
let name: String = "Swift"
let count: Int = 10
let ratio: Double = 0.5
// Optional annotations:
var optional: String? = nil
// Collection annotations:
var list: [Int] = [1, 2]
var dict: [String: Int] = [:]
// Tuple annotations:
var pair: (Int, String) = (1, "a")
// Function type annotations:
var handler: (Int) -> Void
// When needed:
// empty collections cannot be inferred
// protocol types, generics
// public APIs must be annotated
// helps documentation and compile-time checks

Constants

`let` declares a constant. A reference-type constant is an immutable reference. Compile-time constants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Basic constants:
let days = 7
// Constant arrays/dictionaries:
let fixed = [1, 2, 3]
// fixed.append(4) // Error: immutable
// Constants of reference type:
class Counter { var n = 0 }
let c = Counter()
// c is immutable, but the object's properties are mutable:
c.n += 1 // allowed
// Global constants:
let appName = "GuruToolkit"
// Compile-time constants:
// static let / enum as namespace
enum Config {
static let maxRetry = 3
}
print(Config.maxRetry)
// Constants can be computed:
let total = days * 24

Shadowing

An inner scope's same-named variable shadows the outer one. Common with `if` / `switch` local constants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let x = 10
// Inner shadowing:
if true {
let x = 20 // shadows outer
print(x) // 20
}
print(x) // 10
// Common pattern:
// optional binding with same name:
var value: Int? = 5
if let value = value {
print(value) // new constant 5
}
// Loop variable shadowing:
for x in 1...3 {
print(x) // 1 2 3
}
// Caveats:
// Shadowing hurts readability
// avoid multi-layer shadowing in complex functions
// optional binding uses shadowing for safe unwrapping

Scope

`{}` defines scope. Inner scope can access outer. Outer scope cannot access inner.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let outer = 1
// Block scope:
{
let inner = 2
print(outer) // inner can access outer
print(inner)
}
// print(inner) // Error: out of scope
// Function scope:
func demo() {
let local = "inside function"
print(local)
}
// Loop scope:
for i in 1...3 {
let sq = i * i
print(sq)
}
// Same-name rules:
// cannot redeclare in the same scope
// nested scopes may shadow
// Lifetime:
// variables are destroyed when the scope ends
// globals live for the lifetime of the program

Naming conventions

Swift naming conventions: camelCase, clear verbs, avoid abbreviations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Variables/constants: camelCase
let userName = "nick"
var totalCount = 0
// Types: CamelCase
struct UserAccount {}
class NetworkManager {}
enum ColorChoice {}
// Protocols: verbs or adjectives
protocol CanGreet {}
protocol Named {}
// Boolean properties: is/has prefix
var isEnabled = true
var hasPermission = false
// Methods: start with a verb
func saveData() {}
func loadConfig() {}
// Official API design guidelines:
// Names should read as a sentence
// Avoid abbreviations and ambiguity
// Use /// doc comments to explain purpose

Type conversion

Numeric types require explicit conversion. `init()` constructors. Convert between strings and numbers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Numeric conversion (explicit):
let i = 42
let d = Double(i) // 42.0
let u = UInt8(i) // 42
// No automatic implicit conversion:
// let x = 1 + 2.0 // Error
let x = Double(1) + 2.0 // correct
// String to number:
let s = "42"
let n = Int(s) // Int? optional
let f = Double("3.14") // Double?
// Number to string:
let str = String(42)
let str2 = "\(3.14)"
// Optional unwrap:
if let n = Int(s) {
print(n)
}
// Truncation: Int(3.9) // 3

3.Type system

Basic types, tuples, structs, enums, and optionals.

Basic types

`Int`, `Double`, `Bool`, `String`, `Character`. Value types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Integers:
let i: Int = -10
let u: UInt = 10
// Floating point:
let f: Float = 1.5 // 32-bit
let d: Double = 3.14159 // 64-bit
// Boolean:
let t: Bool = true
let f2: Bool = false
// Strings and characters:
let s: String = "hello"
let c: Character = "A"
// Type alias:
let whole: Int = 42
// Integer range:
print(Int.min, Int.max)
// Standard library types:
// Int/Double are value types
// assignment copies, not references
// method call: i.description

Integer types

`Int8`–`Int64` and `UInt` families. Platform `Int`. Overflow handling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Signed: Int8 Int16 Int32 Int64
// Unsigned: UInt8 UInt16 UInt32 UInt64
// Platform default: Int (64-bit)
let a: Int8 = -128
let b: UInt8 = 255
let c: Int64 = 9_223_372_036_854_775_807
// Underscore separators:
let big = 1_000_000
// Overflow:
// overflow errors by default (compile-time literals)
// use overflow operators for arithmetic overflow:
var x: UInt8 = 250
x = x &+ 10 // wraps to 4
// &- &* &/ overflow operators
// Check overflow:
// x.addingReportingOverflow(10)
// Annotate types when precedence is ambiguous

Floating-point

`Float` / `Double` precision. `CGFloat` for UI coordinates. Arithmetic and special values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Double 64-bit / Float 32-bit
let d = 3.141592653589793
let f: Float = 3.14
// Scientific notation:
let e = 1.5e3 // 1500.0
// Literals are inferred as Double
// Arithmetic:
let sum = d + 1.0
let sqrt = d.squareRoot()
let pow = pow(2.0, 10.0)
// Special values:
let nan = Double.nan
let inf = Double.infinity
nan.isNaN // true
inf.isInfinite // true
// Comparison:
// floating-point equality needs tolerance
let a = 0.1 + 0.2 // 0.30000000000000004
// Formatting:
String(format: "%.2f", a)
// Rounding:
Int(3.7) // 3
3.7.rounded() // 4.0

Tuples

Compound multi-element values. Named elements. Destructuring. Lightweight data grouping.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Basic tuple:
let pair = (1, "one")
print(pair.0) // 1
print(pair.1) // "one"
// Named elements:
let user = (name: "Nick", age: 30)
print(user.name)
print(user.age)
// Destructuring:
let (code, message) = (200, "OK")
print(code)
// Ignoring parts:
let (x, _) = (1, 2)
// Type annotations:
let point: (x: Double, y: Double) = (0, 0)
// Functions returning multiple values:
func div(a: Int, b: Int) -> (q: Int, r: Int) {
(a / b, a % b)
}
let result = div(a: 10, b: 3)
print(result.q, result.r)
// Iterating a dictionary destructures tuples

Structs

`struct` is a value type. Auto-generated memberwise initializer. Properties and methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Define a struct:
struct Point {
var x: Double
var y: Double
// Methods:
func distance() -> Double {
(x * x + y * y).squareRoot()
}
// Mutating method:
mutating func moveBy(dx: Double) {
x += dx
}
}
// Automatic initializer:
let p = Point(x: 1, y: 2)
print(p.distance())
// Value-type copy:
var p2 = p
p2.x = 100
print(p.x) // 1, p is unaffected
// See the OOP section for computed properties
// Prefer struct over class

Enums

`enum` groups related values. Associated values. Raw values. Exhaustive matching.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Simple enum:
enum Direction {
case north, south, east, west
}
// Associated values:
enum Result2 {
case success(Int)
case failure(String)
}
// Raw values:
enum Color: String {
case red = "#FF0000"
case green = "#00FF00"
}
// Usage:
let dir = Direction.north
switch dir {
case .north: print("N")
case .south: print("S")
case .east: print("E")
case .west: print("W")
}
// Extracting associated values:
let r = Result2.success(200)
if case .success(let v) = r {
print(v)
}
// Raw values:
print(Color.red.rawValue)
// Methods can be added to enums

Optionals

`Optional` represents a possibly-empty value. `T?` syntax. `nil` means no value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Declare an optional:
var name: String? = nil
var age: Int? = 30
// Assignment:
name = "Nick"
// Unwrapping methods:
// 1. Force unwrap (dangerous):
// print(name!)
// 2. Optional binding:
if let n = name {
print(n)
}
// 3. Nil-coalescing operator:
let display = name ?? "unknown"
// 4. Optional chaining:
// name?.count
// Check for nil:
if name == nil { print("no name") }
// Underlying type:
// Optional<String> is an enum
// with .some and .none

Nested types

Types defined inside other types. Namespace organization. Enums carrying related types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Nested types:
struct Player {
// enum nested in struct:
enum State {
case idle, running, dead
}
var state: State = .idle
// Nested type alias:
typealias Health = Int
var health: Health = 100
}
// Usage:
let p = Player()
let s: Player.State = .running
print(s)
// Access nested types:
// Player.State
// Nested types for organization:
enum HTTP {
enum Status {
static let ok = 200
static let notFound = 404
}
}
print(HTTP.Status.ok)
// Benefits:
// related types defined close together
// fewer top-level name collisions
// expresses the "belongs to" relationship
// keep nesting reasonable

4.Value types & references

Value semantics, class references, copy-on-write, and memory layout.

Value vs reference

`struct` copies by value semantics. `class` shares by reference. Core language difference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Value type:
struct Point { var x = 0 }
var a = Point()
a.x = 10
var b = a // copy
b.x = 99
print(a.x) // 10, independent
// Reference type:
class Box { var value = 0 }
let c = Box()
c.value = 5
let d = c // shared reference
d.value = 100
print(c.value) // 100, same object
// Selection guidelines:
// prefer value types by default
// use class for sharing/inheritance
// use value types for equality semantics
// struct dominates in Swift

`class` semantics

`class` is a reference type. Identity is unique. Mutability and thread sharing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Class definition:
class Counter {
var count = 0
}
// Shared reference:
let c1 = Counter()
let c2 = c1
c2.count += 1
print(c1.count) // 1, same object
// Identity comparison:
// === compares reference identity
if c1 === c2 { print("same instance") }
// == compares values (requires Equatable)
// Mutability of reference types:
// a constant reference can still modify properties
// sharing across threads requires synchronization
// classes support inheritance (see OOP section)
// classes have deinit

Copy-on-write

Value types like `Array` share storage internally. Copy happens only on mutation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Arrays:
var a = [1, 2, 3]
var b = a // share internal storage
print(a) // [1, 2, 3]
// Copy only on write:
b.append(4)
print(a) // [1, 2, 3] unchanged
// Copy-on-write optimization:
// assignment is nearly zero-cost
// the copy is allocated only on the first write
// efficient for assigning large arrays repeatedly
// custom types can be optimized:
// use isKnownUniquelyReferenced
// to check whether a reference is unique
// String/Dictionary get the same optimization
// value semantics + high performance

Identity & equality

`===` is reference identity. `==` is value equality (Equatable). The distinction matters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Identity ===:
class Person { var name: String
init(name: String) { self.name = name }
}
let p1 = Person(name: "Nick")
let p2 = p1
let p3 = Person(name: "Nick")
// Identity:
print(p1 === p2) // true same object
print(p1 === p3) // false different objects
// Value equality (requires Equatable):
struct Point: Equatable {
var x: Int, y: Int
}
let q1 = Point(x: 1, y: 2)
let q2 = Point(x: 1, y: 2)
print(q1 == q2) // true same values
// Rules:
// use === on reference types to check identity
// use == on value types to check equality
// implement custom Equatable
// hash and equality must be consistent (Hashable)

`weak` & `unowned`

`weak` is a weak reference; doesn't bump the reference count. `unowned` is an unowned reference. Breaks retain cycles.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
var pet: Pet?
deinit { print("Person deallocated") }
}
class Pet {
// weak does not retain:
weak var owner: Person?
deinit { print("Pet deallocated") }
}
var nick: Person? = Person()
var dog: Pet? = Pet()
nick?.pet = dog
dog?.owner = nick
// Retain cycle broken:
nick = nil // deallocated
print("nick has been deallocated")
// weak must be var and optional
// unowned: assume the value always exists
// class Restaurant {
// unowned var chef: Chef
// }
// accessing a deallocated unowned crashes
// prefer weak

`inout` parameters

`inout` parameters are passed by reference. The function modifies the original value. Prefix with `&` at the call site.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// inout function:
func swapValues(_ a: inout Int, _ b: inout Int) {
let tmp = a
a = b
b = tmp
}
// Call:
var x = 1
var y = 2
swapValues(&x, &y)
print(x, y) // 2 1
// Modifying an array element:
func increment(_ n: inout Int) {
n += 1
}
var nums = [10, 20]
increment(&nums[0])
print(nums) // [11, 20]
// Restrictions:
// cannot pass literals/constants
// cannot pass the same variable twice
// properties must be var
// Difference from reference-type parameters:
// inout is a value in/out channel

Pointer interop

`UnsafePointer` for C interop. Memory-management responsibility. Hazardous territory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Get a pointer to a variable:
var number = 42
// withUnsafePointer scope:
withUnsafePointer(to: &number) { ptr in
print(ptr.pointee) // 42
}
// Mutable pointer:
withUnsafeMutablePointer(to: &number) { ptr in
ptr.pointee = 100
}
print(number) // 100
// C interoperability:
// pass an array as a C pointer:
var arr = [1, 2, 3]
arr.withUnsafeBufferPointer { buf in
// buf.baseAddress is passed to C
}
// Caveats:
// pointer lifetime must be managed manually
// do not keep a pointer that escapes the scope
// avoid in most situations
// only consider when there is a performance bottleneck

Memory layout

A type's memory layout. Byte alignment. Affects performance and interop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// Inspect a type's size:
print(MemoryLayout<Int>.size) // 8
print(MemoryLayout<Double>.size) // 8
print(MemoryLayout<Bool>.size) // 1
// Alignment:
print(MemoryLayout<Int>.alignment)
// Struct layout:
struct Small {
let a: Int8 // 1 byte
let b: Int64 // 8 bytes
}
print(MemoryLayout<Small>.size) // includes padding
// Compact layout:
// field order affects padding
// place larger fields first for a tighter layout
// Optimization:
// avoid huge numbers of tiny structs
// arrays are contiguous
// mind the layout when interoperating with C
// manual optimization is rarely needed

5.Control flow

`if`, `switch`, `guard`, loops, and optional binding.

`if` / `else`

`if` executes conditionally. `else if` for extra branches. Conditions need no parentheses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let score = 85
// Basic if:
if score >= 60 {
print("pass")
}
// else if:
if score >= 90 {
print("excellent")
} else if score >= 60 {
print("pass")
} else {
print("fail")
}
// Conditions must be Bool:
// if score { } // Error
// Multiple conditions:
if score >= 60 && score < 90 {
print("good")
}
// One-line if:
if score > 80 { print("high score") }
// Ternary operator:
let label = score >= 60 ? "pass" : "fail"

`switch`

`switch` matches exhaustively. Ranges, tuples, bindings. No `break` needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let value = 5
// Value matching:
switch value {
case 1, 2, 3:
print("small")
case 4...6:
print("medium")
default:
print("large")
}
// Ranges and where:
let temp = 25
switch temp {
case 0...10: print("cold")
case 11...30 where temp >= 20: print("warm")
case 11...30: print("mild")
default: print("hot")
}
// Exhaustive enums:
enum Grade { case a, b, c }
let g = Grade.a
switch g {
case .a: print("excellent")
case .b: print("good")
case .c: print("pass")
}
// Tuple matching:
// switch (x, y) { case (0, 0): ... }

`guard`

`guard` exits early. Its `else` must exit the scope. The `else` block can have further branches.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// guard for early return:
func validate(_ age: Int?) {
guard let age = age else {
print("age is nil")
return
}
// age is unwrapped here
guard age >= 18 else {
print("underage")
return
}
print("adult, age \(age)")
}
validate(nil)
validate(15)
validate(20)
// Difference from if let:
// guard's unwrapped value remains available
// if let's value is only available within the branch
// guard's else must exit:
// return/throw/break/continue
// structure is clearer, indentation stays shallow
// main flow stays at the top level

`for`-`in` loops

`for`-`in` iterates over ranges, arrays, and dictionaries. Indexed iteration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Ranges:
for i in 1...5 { // includes 5
print(i)
}
for i in 1..<5 { // excludes 5
print(i)
}
// Arrays:
let names = ["a", "b", "c"]
for name in names {
print(name)
}
// With index:
for (i, name) in names.enumerated() {
print("\(i): \(name)")
}
// Dictionaries:
let scores = ["a": 90, "b": 80]
for (key, value) in scores {
print("\(key): \(value)")
}
// Reverse order:
for i in (1...3).reversed() {}
// Step:
for i in stride(from: 0, to: 10, by: 2) {}
// Index into array:
for i in names.indices { print(names[i]) }

`while` loops

`while` is a conditional loop. `repeat`-`while` executes first, then tests the condition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var n = 3
// while:
while n > 0 {
print(n)
n -= 1
}
// repeat-while (runs at least once):
var count = 0
repeat {
count += 1
print("iteration \(count)")
} while count < 3
// Exiting a loop:
var found = false
var i = 0
while !found && i < 10 {
i += 1
if i == 5 { found = true }
}
// Use cases:
// loops with unknown conditions
// polling, retry logic
// Difference from for-in:
// use while when the count is unknown
// repeat-while fits menu-style interactions

`break` & `continue`

`break` exits a loop. `continue` skips the current iteration. Labeled statements break out of nested loops.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// continue:
for i in 1...10 {
if i % 2 == 0 {
continue // skip even numbers
}
print(i) // 1 3 5 7 9
}
// break:
for i in 1...10 {
if i > 5 {
break // exit early
}
print(i)
}
// Label to exit nested loops:
outer: for i in 1...3 {
for j in 1...3 {
if i * j == 6 {
print("hit \(i) \(j)")
break outer // exit all
}
}
}
// Label can be any name
// useful for deeply nested control flow
// use sparingly; overuse hurts readability

`if let`

Optional binding unwrap. guard let exits early. Unpack multiple values simultaneously.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var name: String? = "Nick"
var age: Int? = 30
// Single unwrap:
if let name = name {
print("name \(name)")
}
// Multiple unwraps (comma):
if let name = name, let age = age {
print("\(name) is \(age) years old")
}
// With a condition:
if let name = name, name.count > 2 {
print(name)
}
// Unwrapping in a while:
var nums = [1, 2, 3]
while let n = nums.popLast() {
print(n) // 3 2 1
}
// Only checking nil, ignoring the value:
if name != nil { print("has value") }
// Caveats:
// if let value is not available outside the branch
// use guard let if you need it later

Pattern matching

Patterns in switch/if. case let, where. Type matching.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Binding associated values:
enum Result3 {
case success(Int)
case failure(String)
}
let r = Result3.success(200)
switch r {
case .success(let code):
print("success \(code)")
case .failure(let msg):
print("failure \(msg)")
}
// Range matching:
let grade = 85
switch grade {
case 90...100: print("A")
case 80..<90: print("B")
case 70..<80: print("C")
default: print("D")
}
// Type matching:
let any: Any = 42
switch any {
case let i as Int: print("integer \(i)")
case let s as String: print("string \(s)")
default: print("other")
}
// where conditions:
switch grade {
case let g where g % 5 == 0: print("multiple of 5")
default: print("other score")
}

6.Functions

Function definitions, parameter labels, return values, function types, and closures.

Function definition

func defines functions. Parameters and return values. Call syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Basic function:
func greet(name: String) -> String {
"Hello, \(name)!"
}
// No return value:
func sayHello() {
print("hello")
}
// Can also mark as Void:
func log(_ msg: String) -> Void {}
// Call:
let g = greet(name: "Nick")
print(g)
sayHello()
// Multiple parameters:
func add(a: Int, b: Int) -> Int {
a + b
}
// Default argument label = parameter name
// label required at the call site
// the last expression is the return value

Parameter labels

Parameter labels and internal names. _ omits the label. Readability design.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Custom labels:
func move(from start: Int, to end: Int) {
print("from \(start) to \(end)")
}
move(from: 1, to: 10)
// Omit the label:
func sum(_ a: Int, _ b: Int) -> Int {
a + b
}
print(sum(1, 2))
// Mixing:
func configure(_ name: String, debug: Bool = false) {
print(name, debug)
}
configure("app") // label omitted
configure("app", debug: true)
// API design guidelines:
// labels make calls read as sentences
// prepositions like with/for/in are common
// choose clear labels to avoid ambiguity

Default parameter values

Default parameter values. Omitted at call site. Must be trailing, or all must have defaults.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Default values:
func greet2(_ name: String, times: Int = 1) {
for _ in 1...times {
print("Hello \(name)")
}
}
greet2("Nick") // uses default 1
greet2("Nick", times: 3)
// Common usage of defaults:
func fetch(
url: String,
timeout: Double = 30,
retries: Int = 3
) {
print(url, timeout, retries)
}
// Call with any combination:
fetch(url: "https://x")
fetch(url: "https://x", retries: 5)
// Rules:
// defaults must be at the end of the parameter list
// defaulted arguments can be omitted
// defaults are visible in the function signature
// balances readability and flexibility

Return values

Return value type. Use tuples for multiple values. Implicit return for single expressions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Single return value:
func square(_ n: Int) -> Int {
n * n
}
// Tuple multiple return values:
func stats(_ nums: [Int]) -> (min: Int, max: Int, sum: Int) {
(nums.min() ?? 0, nums.max() ?? 0, nums.reduce(0, +))
}
let s = stats([3, 1, 4, 1])
print(s.min, s.max, s.sum)
// Optional return value:
func findIndex(of value: Int, in arr: [Int]) -> Int? {
arr.firstIndex(of: value)
}
if let i = findIndex(of: 4, in: [1, 2, 4]) {
print(i) // 2
}
// No return value:
func noReturn() {}
// Implicit return:
// omit return for a single expression
// use explicit return for multiple statements

Variadic parameters

... collects multiple arguments into an array. Any count. Last parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Variadic parameters:
func average(_ numbers: Double...) -> Double {
let sum = numbers.reduce(0, +)
return numbers.isEmpty ? 0 : sum / Double(numbers.count)
}
print(average(1, 2, 3, 4)) // 2.5
print(average()) // 0
// Variadic is usable as an array
// Print any number of items:
func logAll(_ items: String...) {
for item in items {
print(item)
}
}
logAll("a", "b", "c")
// Rules:
// variadic must come last
// at most one per function
// internally it is a [T] array
// arguments are separated by commas at the call site

`inout` parameters

inout modifies external variables. & passes by reference. Mutate value types in place.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Swap:
func swapTwo<T>(_ a: inout T, _ b: inout T) {
let tmp = a
a = b
b = tmp
}
var x = 5
var y = 10
swapTwo(&x, &y)
print(x, y) // 10 5
// Modifying a value type:
func scale(_ v: inout Double, by factor: Double) {
v *= factor
}
var price = 100.0
scale(&price, by: 0.8)
print(price) // 80.0
// Rules:
// pass variables, not constants/literals
// & prefix
// Difference from reference types:
// inout is a round-trip value through the function
// no new instance is created
// great for large value types to avoid copies

Function types

Functions as types. Assign, pass as argument, return. First-class citizens.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Function type annotation:
func add(a: Int, b: Int) -> Int { a + b }
// (Int, Int) -> Int
// Assignment:
let operation: (Int, Int) -> Int = add
print(operation(1, 2))
// As a parameter:
func apply(_ f: (Int) -> Int, to value: Int) -> Int {
f(value)
}
func double(_ n: Int) -> Int { n * 2 }
print(apply(double, to: 5)) // 10
// Returning a function:
func makeAdder(_ base: Int) -> (Int) -> Int {
{ n in base + n }
}
let addTen = makeAdder(10)
print(addTen(5)) // 15
// Optional function type:
var handler: (() -> Void)?
// Handle the optional when calling
// type matching is strict (labels included)

Closures

Closures capture surrounding context. Trailing closures. Shorthand argument names.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Define a closure:
let square2 = { (n: Int) -> Int in
n * n
}
print(square2(4))
// Capturing variables:
var counter = 0
let increment = {
counter += 1
}
increment()
increment()
print(counter) // 2
// Trailing closures:
func perform(_ action: () -> Void) {
action()
}
perform {
print("trailing closure")
}
// Sorting shorthand:
let nums = [3, 1, 2]
let sorted = nums.sorted { $0 < $1 }
print(sorted)
// Shorthand:
// $0 $1 parameter shorthand
// capture list [weak self]
// @escaping for escaping closures

7.Strings

String operations, interpolation, substrings, Unicode, and formatting.

String basics

String is a value type. Literals and mutability. Composed of Characters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Create a string:
let greeting = "Hello"
// Multi-line string: triple-quoted literal
// let multi = """
// multi
// line
// """
// Equivalent (with \n):
let multi = "multi\nline"
// Mutable string:
var text = "start"
text += " end"
// Empty strings:
let empty = ""
let empty2 = String()
// Character sequence:
// String is composed of Character
// Iterate characters:
for c in text {
print(c)
}
// Count (number of characters):
print(text.count)
// Check empty:
text.isEmpty
// String is a value type:
// assignment copies
// efficient (copy-on-write)

Interpolation

\(expression) embeds values. Type-safe. Any expression.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let name = "Nick"
let age = 30
let score = 95.5
// Basic interpolation:
let msg = "\(name) is \(age) years old"
print(msg)
// Expression interpolation:
let calc = "\(age * 2) is twice \(age)"
// Calling methods:
let upper = "uppercase: \(name.uppercased())"
// Formatted values:
let formatted = "score: \(String(format: "%.1f", score))"
// Nested interpolation:
let nested = "\(name) has \("\(age)".count) characters"
// Escape backslash:
let backslash = "backslash:\\"
// Caveats:
// interpolated types must be displayable
// custom types implement CustomStringConvertible

Concatenation

String concatenation. append. join. += operator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// + concatenation:
let a = "hello"
let b = " world"
let c = a + b // new string
// += append:
var s = "start"
s += " - more"
// append:
s.append("!")
// character append:
s.append(Character("?"))
// array join:
let parts = ["a", "b", "c"]
let joined = parts.joined(separator: ", ")
print(joined) // "a, b, c"
// performance:
// small strings are cheap to concatenate
// many concatenations:
// use array + joined
// or build once with a mutable string
// strings are value types; concatenation creates a new value

Multiline strings

Triple-quoted multiline strings. Indent stripping. Inline interpolation. Line breaks preserved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// multi-line strings use triple-quoted literals:
// let poem = """
// Moonlight before the bed,
// Frost upon the ground.
// """
// equivalent (with \n newline):
let poem = "Moonlight before the bed,\nFrost upon the ground."
print(poem)
// indentation rule: based on the closing quote indent
// embedding quotes:
// let quote = """
// He said "hi"
// """
// multi-line interpolation:
let name = "Rust"
// let info = """
// Language: \(name)
// Type: static
// """
// backslash continues a line:
// let oneLine = """
// one line \
// continues
// """

Common methods

Case changes, search, replace, split, trim. Common string APIs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let s = " Hello, Swift "
// case:
let lower = s.lowercased()
let upper = s.uppercased()
// trim:
let trimmed = s.trimmingCharacters(
in: .whitespacesAndNewlines)
// check:
s.contains("Swift")
s.hasPrefix(" ")
s.hasSuffix(" ")
// find:
s.firstIndex(of: ",")
// replace:
let replaced = s.replacingOccurrences(
of: "Swift", with: "Go")
// split:
let parts = "a,b,c".split(separator: ",")
// prefix/suffix:
let pre = s.prefix(5)
// isEmpty check: s.isEmpty
// repeat:
String(repeating: "ab", count: 3)

Substrings

Substring references the original. Slices return Substring. Convert to String to keep.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let full = "Hello, Swift World"
// slice via index range:
let start = full.index(full.startIndex, offsetBy: 7)
let end = full.index(start, offsetBy: 5)
let sub = full[start..<end] // "Swift"
print(sub)
// note: Substring type
// shares storage with the original string
// convert to String to own a copy:
let owned = String(sub)
// prefix/suffix return Substring:
let p = full.prefix(5) // "Hello"
// slice with Range:
if let range = full.range(of: "Swift") {
print(full[range])
}
// multibyte-safe:
// indices walk grapheme cluster boundaries
// don't use integer subscripts
// convert to String for frequent access

Unicode

Characters composed of Unicode scalars. Code points. Extended grapheme clusters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Unicode literal:
let heart = "\u{2764}"
print(heart) // ❤
// extended grapheme clusters:
let e = "\u{E9}" // é
let combo = "e\u{301}" // é (combining)
print(e == combo) // true, same grapheme cluster
// character (grapheme cluster) count:
let flag = "🇨🇳"
print(flag.count) // 1 (regional indicators combined)
// iterate grapheme clusters:
for c in "ab👍" {
print(c)
}
// codepoint:
for scalar in "A".unicodeScalars {
print(scalar.value) // 65
}
// normalization:
// .precomposedStringWithCanonicalMapping()
// sort/comparison requires normalization

Formatting

String(format:) C-style formatting. Numeric padding and precision.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// basic format:
let pi = 3.14159
let f = String(format: "%.2f", pi)
print(f) // "3.14"
// zero-padded integer:
String(format: "%05d", 42) // "00042"
// width alignment:
String(format: "%10s", "hi") // right-aligned
// scientific notation:
String(format: "%e", 1234.0)
// hexadecimal:
String(format: "%x", 255) // "ff"
// multiple placeholders:
String(format: "%d-%@", 2026, "08-02")
// notes:
// %@ object, %d integer, %f float
// %lld 64-bit integer
// more Swift-y way:
// prefer interpolation and description
// only use format() when precise formatting is needed

8.Collections

Array, Dictionary, Set, higher-order functions, and ranges.

Array

Ordered collection of values. Generic [T]. Add, remove, modify, search.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// creation:
var nums = [1, 2, 3]
let empty: [Int] = []
let filled = [Int](repeating: 0, count: 5)
// literal type inference:
let names = ["a", "b"]
// access:
print(nums[0])
print(nums.first ?? 0)
print(nums.last ?? 0)
// add/remove:
nums.append(4)
nums.insert(0, at: 0)
nums.removeLast()
nums.remove(at: 1)
// iterate:
for n in nums { print(n) }
// other:
nums.count
nums.isEmpty
nums.contains(2)
// sum:
let sum = nums.reduce(0, +)
// sort:
nums.sorted()

Array operations

Slice, replace, merge, search. Common higher-order methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var arr = [1, 2, 3, 4, 5]
// slice:
let slice = arr[1...3] // ArraySlice
print(slice)
// replace range:
arr.replaceSubrange(0..<2, with: [9, 9])
// combine:
let combined = arr + [10]
// find:
arr.first { $0 > 3 }
arr.last { $0 > 3 }
arr.contains(5)
arr.firstIndex(of: 3)
// batch transform:
let doubled = arr.map { $0 * 2 }
let evens = arr.filter { $0 % 2 == 0 }
// partition:
let (small, big) = arr.partitioned {
$0 < 3
}
// prefix/suffix:
arr.prefix(2)
arr.suffix(2)
// swap:
arr.swapAt(0, 1)

Dictionary

Key-value collection. [String: T]. Unordered. O(1) lookup.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// creation:
var scores: [String: Int] = [:]
var config = ["env": "dev", "port": "8080"]
// add/update:
scores["alice"] = 90
scores["bob"] = 85
scores["alice"] = 95 // overwrite
// read (optional):
let alice = scores["alice"] // Int?
let missing = scores["x"] // nil
// default value:
let v = scores["x"] ?? 0
// remove:
scores["bob"] = nil
scores.removeValue(forKey: "alice")
// iterate:
for (k, v) in scores {
print("\(k): \(v)")
}
// keys/values arrays:
Array(scores.keys)
Array(scores.values)
// merge:
config.merge(["port": "9090"]) { _, new in new }
// count and empty:
scores.count

Set

Unordered collection of unique elements. Hash set. Set operations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// creation:
var set: Set<Int> = [1, 2, 3]
let other: Set = [3, 4, 5]
// add/remove/contains:
set.insert(4)
set.remove(1)
set.contains(2)
// set operations:
let union = set.union(other) // union
let intersect = set.intersection(other) // intersection
let diff = set.subtracting(other) // difference
let sym = set.symmetricDifference(other) // symmetric difference
// relationship checks:
set.isSubset(of: other)
set.isSuperset(of: other)
set.isDisjoint(with: other)
// iterate:
for v in set { print(v) }
// to array:
let arr = Array(set)
// use cases:
// deduplication, membership tests
// deduplicate:
let dup = Array(Set([1, 1, 2, 3]))
print(dup)

Iteration

Iterate arrays, dictionaries, sets. With index. Reverse. Filtered iteration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let items = ["a", "b", "c"]
// basic iteration:
for item in items {
print(item)
}
// with index:
for (i, item) in items.enumerated() {
print("\(i): \(item)")
}
// index-based iteration:
for i in items.indices {
print(items[i])
}
// reverse:
for item in items.reversed() {}
// dictionary iteration:
let dict = ["a": 1, "b": 2]
for (k, v) in dict {
print("\(k): \(v)")
}
// conditional iteration:
for n in 1...10 where n % 2 == 0 {
print(n) // 2 4 6 8 10
}
// skip and exit:
// continue / break
// forEach:
items.forEach { print($0) }

Higher-order functions

map, filter, reduce, compactMap, sorted. Functional processing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let nums = [1, 2, 3, 4, 5]
// map:
let doubled = nums.map { $0 * 2 }
// filter:
let evens = nums.filter { $0 % 2 == 0 }
// reduce:
let sum = nums.reduce(0, +)
// compactMap (drop nil):
let strs = ["1", "a", "3"]
let ints = strs.compactMap { Int($0) }
print(ints) // [1, 3]
// flatMap (flatten):
let grid = [[1, 2], [3]]
let flat = grid.flatMap { $0 }
// sorted:
let sorted = nums.sorted { $0 > $1 }
// chain:
let result = nums
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.reduce(0, +)
print(result) // 4 + 16 = 20
// forEach for iteration
// laziness: lazy.map

Nested collections

Composing collections. Multi-dimensional arrays, dictionaries of arrays, collections of structs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 2D array:
var grid = [[Int]]()
grid.append([1, 2, 3])
grid.append([4, 5, 6])
print(grid[0][1]) // 2
// repeat initialization:
var matrix = [[Int]](
repeating: [Int](repeating: 0, count: 3), count: 3)
matrix[1][2] = 9
// dictionary of arrays:
var groups: [String: [String]] = [:]
groups["dev"].append("nick") // error
// initialize first:
groups["dev"] = ["nick"]
groups["dev", default: []].append("tom")
// collection of structs:
struct Task { var title: String; var done = false }
var tasks: [Task] = [Task(title: "a")]
tasks[0].done = true
// array of dictionaries:
let scores: [[String: Int]] = [
["math": 90], ["eng": 85]
]
print(scores[0]["math"] ?? 0)

Range

Half-open range ..<, closed range .... Array slicing and loops.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// range types:
let closed = 1...5 // includes 5
let halfOpen = 1..<5 // excludes 5
// loop:
for i in 1...3 {
print(i)
}
// array slicing:
let arr = [10, 20, 30, 40]
let sub = arr[1...2] // [20, 30]
// one-sided range:
let head = arr[..<2] // [10, 20]
let tail = arr[2...] // [30, 40]
// contains:
(1...5).contains(3) // true
// stride:
for i in stride(from: 0, to: 10, by: 2) {
print(i) // 0 2 4 6 8
}
// reverse:
for i in (1...3).reversed() {}
// boundaries:
// empty range: 1..<1
// floating-point ranges (limited)
// Range used for subscripts and pattern matching

9.Memory management

ARC reference counting, weak references, retain cycles, and memory optimization.

ARC

Automatic reference counting manages class instances. Deallocated when count hits zero.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Device {
var name = ""
deinit {
print("\(name) released")
}
}
// reference counting:
var d1: Device? = Device() // count 1
d1?.name = "phone"
var d2 = d1 // count 2
var d3 = d1 // count 3
d1 = nil // count 2
d2 = nil // count 1
d3 = nil // count 0, released
// rules:
// each strong reference +1
// count reaches zero, auto-released
// deinit observes release
// value types (struct) don't participate in ARC
// no manual management needed
// weak references don't increment count

weak/unowned

Weak references do not increment count. Prevent retain cycles. Handle access after deallocation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Parent {
var child: Child?
}
class Child {
// weak: doesn't retain parent
weak var parent: Parent?
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent
// cycle broken:
parent = nil
// child's parent automatically becomes nil
// weak properties:
// must be var, must be optional
// automatically nil when referenced object is released
// unowned:
// class CreditCard {
// unowned let owner: Person
// }
// unowned assumes the target always exists
// accessing released unowned crashes
// priority: weak > unowned

Autorelease pool

autoreleasepool defers release. Memory control in large loops.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// large loop:
var results: [String] = []
// wrap each iteration in a pool:
for i in 0..<1000 {
autoreleasepool {
// temporary objects released here:
let temp = "temp data \(i)"
results.append(temp)
// other temporary objects...
}
}
// released at the end of each iteration
// use cases:
// peak memory in large loops
// processing many images/files
// long tasks with temporaries
// notes:
// modern Swift often manages this automatically
// use only when manual control is needed
// return inside the pool releases early
// usually not needed under ARC

Copy semantics

Value types are safe to copy. Optimizing large types. Shared vs. unique.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// value-type copy:
var arr1 = [1, 2, 3]
var arr2 = arr1 // copy-on-write
// mutation triggers a real copy:
arr2.append(4)
print(arr1) // [1, 2, 3] unchanged
// large strings/arrays/dictionaries:
// assignment is cheap (shared storage)
// allocation happens on mutation
// custom structs are equally safe:
struct Config {
var theme = "dark"
var font = 14
}
var c1 = Config()
var c2 = c1
c2.theme = "light"
print(c1.theme) // dark
// copy principles:
// value semantics prevent accidental sharing
// thread-safe (no shared state)
// use class when sharing is needed

Retain cycles

Two classes strongly referencing each other form a cycle. Memory leak. weak breaks it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Owner {
var pet: Pet?
deinit { print("Owner released") }
}
class Pet {
// BAD: strong reference to parent, forms cycle
// var owner: Owner?
// GOOD: weak reference
weak var owner: Owner?
deinit { print("Pet released") }
}
var owner: Owner? = Owner()
var pet: Pet? = Pet()
owner?.pet = pet
pet?.owner = owner
// releases normally without cycle:
owner = nil
pet = nil
// diagnostics:
// Instruments Leaks check
// deinit not called = leak
// other cycle scenarios:
// closures strongly capturing self
// break with [weak self]
// watch Timer/Delegate

Stack vs heap

Value types on the stack, reference types on the heap. Allocation and performance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// stack allocation:
struct Point { var x = 0 }
let p = Point() // stack (usually)
// heap allocation:
class Box { var v = 0 }
let b = Box() // heap
// large struct optimization:
// above a size threshold, also on heap
// array elements stored inline
// performance impact:
// stack allocation is fast, auto-released
// heap allocation needs ARC management
// many small objects:
// prefer struct over class
// struct arrays use contiguous memory:
struct Item { var id = 0 }
let items = [Item(), Item()] // contiguous
// reference:
// value types default to stack
// escape to heap only when needed
// no need to over-think it

lazy storage

lazy defers initialization. Created only when accessed. Optimizes expensive properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// lazy property:
class DataLoader {
// initialized only when accessed:
lazy var config: [String: String] = {
print("loading config")
return ["env": "dev"]
}()
}
let loader = DataLoader()
// not accessed, not executed:
print("created")
// first access triggers init:
print(loader.config)
// subsequent access returns directly:
print(loader.config)
// characteristics:
// initialized only once
// not thread-safe (mind concurrency)
// self is available inside the closure
// good for:
// expensive construction
// properties that may not be used
// initialization depending on other properties
// global variables are also lazy

Memory optimization

Reduce heap allocations, reuse buffers, avoid large copies. Performance tuning.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// reduce temporary allocations:
// reuse a mutable string:
var buffer = ""
for i in 0..<1000 {
// append, don't concatenate:
buffer.append("\(i),")
}
// preallocate capacity:
var nums: [Int] = []
nums.reserveCapacity(1000)
for i in 0..<1000 {
nums.append(i)
}
// avoid unnecessary copies:
// use inout to mutate large arrays
// share via ArraySlice
// mind value-type copies
// pass large structs by reference
// use withUnsafeBytes
// profiling tools:
// Instruments Allocations
// time profiling first
// don't optimize prematurely

10.Classes & protocols

class, inheritance, protocols, extensions, and protocol-oriented design.

class definition

Classes define properties and methods. Reference type. Initializers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Animal {
var name: String
// initializer:
init(name: String) {
self.name = name
}
// method:
func speak() {
print("\(name) makes a sound")
}
}
// instantiate:
let dog = Animal(name: "Wangcai")
dog.speak()
// reference semantics:
let same = dog
same.name = "Xiaohei"
print(dog.name) // Xiaohei
// default property values:
class Counter {
var count = 0
init() {}
}
// deinit:
deinit {
print("instance released")
}
// classes can inherit and extend
// vs. struct
// classes need init to initialize all stored properties

Inheritance

Subclasses inherit from parents. Stored properties, methods. final classes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Vehicle {
var speed = 0
func describe() -> String {
"speed \(speed)"
}
}
// subclass:
class Car: Vehicle {
var wheels = 4
override func describe() -> String {
"car with \(wheels) wheels, \(super.describe())"
}
}
let car = Car()
car.speed = 100
print(car.describe())
// inheritance rules:
// single inheritance
// override keyword required to override
// use super to access parent
// cannot inherit:
// final class can't be subclassed
// private members not visible
// designated/convenience initializers
// prefer composition and protocols

Method overriding

override overrides methods, properties, initializers. super calls the parent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Base {
var label = "base"
func greet() {
print("Base: \(label)")
}
// overridable property:
var value: Int {
get { 10 }
}
}
class Sub: Base {
// override method:
override func greet() {
super.greet() // call parent first
print("Sub extra handling")
}
// override property:
override var value: Int {
get { super.value + 5 }
}
}
let s = Sub()
s.greet()
print(s.value)
// rules:
// override keyword required
// readability:
// final prevents override
// final func prevents further override
// property overrides use get/set/willSet/didSet
// static methods can be overridden

Protocols

protocol defines requirements. Types conform. Core of protocol-oriented design.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// define a protocol:
protocol Greetable {
var name: String { get }
func greet() -> String
}
// conform to the protocol:
struct Person: Greetable {
var name: String
func greet() -> String {
"Hello, \(name)!"
}
}
struct Robot: Greetable {
var name: String
func greet() -> String {
"Beep, \(name) ready"
}
}
// use as protocol type:
let things: [Greetable] = [
Person(name: "Nick"),
Robot(name: "R2"),
]
for t in things {
print(t.greet())
}
// protocol inheritance:
protocol NamedGreetable: Greetable {
var age: Int { get }
}
// default implementations via extensions

Protocol extensions

extension provides default implementations. Constrained extensions. Default methods on protocols.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// protocol:
protocol Describable {
var description: String { get }
}
// default implementation:
extension Describable {
var description: String {
"description: " + Self.self.description
}
}
// constrained extension:
extension Describable where Self: Equatable {
func isSameAs(_ other: Self) -> Bool {
self == other
}
}
// conform a type via extension:
struct Cat: Describable {
var name: String
}
// use default implementation:
let cat = Cat(name: "Miao")
print(cat.description)
// protocol + extension:
// default methods reduce boilerplate
// combine protocol inheritance
// refine with where clauses
// foundation of protocol-oriented programming

Extensions

extension adds methods to existing types. Organizes code by category.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// extend built-in types:
extension String {
// add a method:
func isEmail() -> Bool {
self.contains("@")
}
// computed property:
var wordCount: Int {
self.split(separator: " ").count
}
}
print("[email protected]".isEmail())
print("hi there".wordCount)
// extend your own types:
struct Point { var x: Double }
extension Point {
func doubled() -> Point {
Point(x: x * 2)
}
}
// conform via extension:
extension Point: Equatable {}
// organize by grouping:
// multiple extensions per type
// group by feature
// can't add stored properties
// can't override existing implementation

Property Observers

willSet/didSet observe property changes. UI updates, validation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var user: String = "" {
willSet {
print("about to change to \(newValue)")
}
didSet {
print("changed from \(oldValue) to \(user)")
}
}
user = "Nick"
user = "Tom"
// prints observation logs
// use cases:
// UI binding refresh
// data validation
// change logging
// rules:
// not triggered by initialization
// works on class and struct
// only stored properties are observable
// computed properties use get/set
// willSet/didSet parameters can be named
// mind threads on concurrent access

Computed Properties

get/set compute a value. Nothing is stored. Derived data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct Circle {
var radius: Double
// computed property:
var area: Double {
// read-only:
Double.pi * radius * radius
}
// read-write:
var diameter: Double {
get {
radius * 2
}
set {
radius = newValue / 2
}
}
}
var c = Circle(radius: 5)
print(c.area) // 78.54
print(c.diameter) // 10
c.diameter = 20
print(c.radius) // 10
// characteristics:
// recomputed on every access
// no storage used
// derived values always consistent
// get is required
// set uses newValue
// custom parameter names also work
// vs. stored properties

11.Error Handling

throws, do-catch, try, defer and custom errors.

Error Protocol

The Error protocol marks a type as an error. Enums define error categories.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// error enum:
enum FileError: Error {
case notFound
case noPermission
case invalidData(String)
}
// simple error:
struct SimpleError: Error {
let message: String
}
// categorized error:
enum LoginError: Error {
case emptyUsername
case wrongPassword
case tooManyAttempts
}
// use cases:
// mark throwable errors
// carry information:
// case invalidData(String)
// errors can be Equatable:
// add Equatable conformance
// LocalizedError provides description
// design errors with clear categories

throws

throws marks a throwing function. throw raises an error. Callers must handle it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// throwing function:
func divide(_ a: Int, by b: Int) throws -> Int {
guard b != 0 else {
throw DivideError.zeroDivision
}
return a / b
}
enum DivideError: Error {
case zeroDivision
}
// caller must handle:
// 1. try! is dangerous
// let x = try! divide(1, by: 0)
// 2. try? converts to optional
let x = try? divide(1, by: 0)
print(x) // nil
// 3. try + do-catch
// 4. propagate to caller
func safeDivide() throws -> Int {
try divide(10, by: 2)
}
// chaining throws:
// mark the function throws to pass through

do-catch

try inside a do block. catch handles the error. Multiple branches.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum CalcError: Error {
case zeroDiv
case overflow
}
func compute() throws -> Int {
throw CalcError.zeroDiv
}
// catching:
do {
let result = try compute()
print(result)
} catch CalcError.zeroDiv {
print("divide by zero")
} catch CalcError.overflow {
print("overflow")
} catch {
print("other error: \(error)")
}
// binding the error:
// catch let e as CalcError
// multiple try in one do
// catch from specific to general
// error is an implicit variable
// uncaught:
// propagates to the caller
// without catch, function needs throws

try Variants

try requires a catch. try? converts to an optional. try! forces it (risk of a crash).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func risky() throws -> Int { 42 }
// try: must use do-catch or propagate
// syntax requirement:
func caller() throws {
let v = try risky()
print(v)
}
// try?: failure becomes nil
let a = try? risky() // Int?
print(a ?? 0)
// try!: failure crashes
// suitable when failure is impossible:
let b = try! risky()
print(b)
// example:
// let data = try? Data(contentsOf: url)
// force-unwrap trade-offs:
// try! crashes directly on failure
// use cautiously in production
// OK in tests / when certain
// try? suits "failure is acceptable" cases

Custom Errors

Errors carry context. LocalizedError provides descriptions. Error codes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// with context:
enum NetworkError: LocalizedError {
case timeout(Int)
case serverError(code: Int, message: String)
// localized description:
var errorDescription: String? {
switch self {
case .timeout(let s):
return "timeout after \(s) seconds"
case .serverError(let code, let msg):
return "server \(code): \(msg)"
}
}
}
// use:
func fetch() throws {
throw NetworkError.serverError(code: 500, message: "internal error")
}
do {
try fetch()
} catch let e as NetworkError {
print(e.errorDescription ?? e)
}
// catch any Error:
// catch let err { print(err.localizedDescription) }
// associated values carry context
// protocol composition:
// Equatable for error comparison

defer Cleanup

defer runs when the scope ends. Cleans up resources. Runs in reverse order.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func processFile() throws {
print("open file")
// deferred cleanup:
defer {
print("close file")
}
print("process data")
// defer runs even if an error is thrown mid-way
// throw SomeError()
print("finished normally")
}
try? processFile()
// multiple defers in reverse order:
defer { print("A") }
defer { print("B") }
// prints B then A
// use cases:
// close files / connections
// unlock, restore state
// clean up temporary resources
// runs on early return too
// notes:
// don't read/write return values inside defer

Fatal Errors

fatalError is an unrecoverable crash. precondition checks a condition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// unrecoverable:
// fatalError("cannot continue")
// precondition:
func process(_ n: Int) {
precondition(n > 0, "parameter must be positive")
print(n)
}
process(5)
// process(-1) // crashes (precondition fails)
// debug assertion:
assert(true, "debug-only check")
// others:
// assert active in debug builds
// precondition active always
// when to use:
// invariant violated
// bug that prevents continuation
// fail early during development
// use error handling in production
// don't overuse

Result Type

Result<Success, Failure> makes success or failure explicit. Avoids throws.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Result usage:
func fetchData() -> Result<[String], Error> {
let ok = true
if ok {
return .success(["a", "b"])
} else {
return .failure(NetworkError.timeout(30))
}
}
enum NetworkError: Error {
case timeout(Int)
}
// handling:
let result = fetchData()
switch result {
case .success(let data):
print(data)
case .failure(let error):
print(error)
}
// methods:
// result.map { ... }
// result.flatMap { ... }
// try result.get() converts to throws
// scenarios:
// async callback return
// non-exception error flow
// combine with Result<_, Never>
// store arrays of error results

12.Input and Output

Command-line input, file reading and writing, FileManager and Codable.

Reading Input

readLine reads command-line input. Interactive loops. Parsing numbers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// read a line:
print("Please enter your name: ", terminator: "")
if let name = readLine() {
print("Hello, \(name)!")
}
// read a number:
print("Enter age: ", terminator: "")
if let input = readLine(), let age = Int(input) {
print("Age \(age)")
}
// interactive loop:
while let line = readLine(), !line.isEmpty {
print("Got: \(line)")
}
// piped batch input:
// echo "data" | swift main.swift
// readLine returns nil on EOF
// standard error:
// FileHandle.standardError
// output:
// print goes to standard output

Reading Files

String(contentsOf:) reads a file. Data for binary. Reading line by line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// read as String:
let path = "data.txt"
do {
let content = try String(contentsOfFile: path, encoding: .utf8)
print(content)
} catch {
print("read failed: \(error)")
}
// read as Data:
let data = try? Data(contentsOf: URL(fileURLWithPath: path))
// process line by line:
let text = "line1\nline2\n"
for line in text.components(separatedBy: .newlines) {
print(line)
}
// relative paths:
// based on the current working directory
// sandbox environments need the Documents directory
// encodings:
// .utf8 / .unicode
// streaming large files:
// FileHandle + read(upToCount:)

Writing Files

Writing strings and Data. Append mode. Atomic writes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
let content = "hello\nworld\n"
// overwrite:
let url = URL(fileURLWithPath: "out.txt")
try? content.write(to: url, atomically: true, encoding: .utf8)
// write Data:
let data = Data(content.utf8)
try? data.write(to: url)
// append:
if let handle = try? FileHandle(forWritingTo: url) {
handle.seekToEndOfFile()
handle.write(Data("more\n".utf8))
try? handle.close()
}
// path handling:
let dir = URL(fileURLWithPath: "data")
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
// error handling:
// try? ignores errors
// use do-catch in production
// atomic writes prevent corruption

FileManager

File system operations. Create directories, delete, move, check existence.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
let fm = FileManager.default
// check existence:
let exists = fm.fileExists(atPath: "data.txt")
// create directory:
try? fm.createDirectory(
atPath: "data/sub", withIntermediateDirectories: true)
// move/rename:
try? fm.moveItem(atPath: "a.txt", toPath: "b.txt")
// copy:
try? fm.copyItem(atPath: "b.txt", toPath: "c.txt")
// delete:
try? fm.removeItem(atPath: "c.txt")
// list directory:
if let items = try? fm.contentsOfDirectory(atPath: ".") {
print(items)
}
// attributes:
if let attrs = try? fm.attributesOfItem(atPath: "data.txt") {
print(attrs[.size] ?? "?")
}
// recursive directory walk:
// enumerator(atPath:)
// sandbox paths:
// fm.urls(for: .documentDirectory, in: .userDomainMask)

Output Control

print variants. stderr. Separator and terminator. String descriptions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// basics:
print("plain output")
print(1, 2, 3) // space-separated
print("a", "b", separator: "-")
print("x", terminator: "") // no newline
print() // newline
// standard error:
FileHandle.standardError.write(
Data("error message\n".utf8))
// debug description:
debugPrint("debug")
// custom output:
struct Item: CustomStringConvertible {
var name: String
var description: String {
"Item(\(name))"
}
}
print(Item(name: "a"))
// CustomDebugStringConvertible:
// more detailed in debug
// dump for deep printing:
dump([1, 2, 3])

Working with Data

Data is a byte container. Converting to and from strings. base64 encoding and decoding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// String to Data:
let str = "hello"
let data = Data(str.utf8)
// Data to String:
let back = String(data: data, encoding: .utf8)
print(back ?? "")
// base64:
let encoded = data.base64EncodedString()
print(encoded) // aGVsbG8=
let decoded = Data(base64Encoded: encoded)
// byte operations:
var bytes: [UInt8] = [0x68, 0x69]
let d = Data(bytes)
print(Array(d)) // [104, 105]
// append:
var buffer = Data()
buffer.append(Data([1, 2]))
buffer.append(Data("a".utf8))
// hex:
let hex = data.map { String(format: "%02x", $0) }.joined()
print(hex) // 68656c6c6f
// read sub-range:
// data.subdata(in: 0..<2)

JSONSerialization

JSON to dictionaries/arrays. Serializing back. The JSONSerialization class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// JSON string:
let jsonStr = "{\"name\": \"Nick\", \"age\": 30, \"tags\": [\"dev\"]}"
let jsonData = Data(jsonStr.utf8)
// parse to Any:
let obj = try? JSONSerialization.jsonObject(
with: jsonData)
// to dictionary:
if let dict = obj as? [String: Any] {
print(dict["name"] ?? "")
let age = dict["age"] as? Int ?? 0
let tags = dict["tags"] as? [String] ?? []
print(age, tags)
}
// reverse serialization:
let payload: [String: Any] = [
"name": "Tom", "age": 25
]
if JSONSerialization.isValidJSONObject(payload) {
let data = try? JSONSerialization.data(withJSONObject: payload)
let str = String(data: data!, encoding: .utf8)
print(str ?? "")
}
// modern recommendation: Codable

Codable

Codable serializes automatically. JSONEncoder/Decoder. Protocol driven.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// type conforming to Codable:
struct User: Codable {
var name: String
var age: Int
var tags: [String]
}
// encoding:
let user = User(name: "Nick", age: 30, tags: ["dev"])
let encoder = JSONEncoder()
let data = try? encoder.encode(user)
let json = String(data: data!, encoding: .utf8)
print(json ?? "")
// decoding:
let jsonStr = "{\"name\": \"Tom\", \"age\": 25, \"tags\": []}"
let decoder = JSONDecoder()
if let u = try? decoder.decode(User.self, from: Data(jsonStr.utf8)) {
print(u.name)
}
// field mapping:
// rename with CodingKeys
// snake_case:
// decoder.keyDecodingStrategy = .convertFromSnakeCase
// array decoding: [User].self
// date handling needs a strategy

13.Common Pitfalls

The traps Swift beginners fall into most often, and the correct way to write it.

Force Unwrapping

Unwrapping nil with ! crashes. Prefer optional binding and the nil-coalescing operator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// BAD: force unwrap
var name: String? = nil
// print(name!) // crash
// GOOD: optional binding
if let name = name {
print(name)
}
// GOOD: nil-coalescing operator
let display = name ?? "anonymous"
print(display)
// GOOD: guard for early unwrap
func show(_ v: String?) {
guard let v = v else {
print("no value")
return
}
print(v)
}
// when is ! safe:
// certain non-nil (already assigned)
// tests / prototypes
// otherwise avoid
// try! same caution
// crash messages are ugly
// be sparing with ! in production code

Optional Chaining

?. chains access. If any link is nil the whole result is nil. Assignment works too.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Address { var city = "Beijing" }
class Person {
var address: Address?
}
let p = Person()
// GOOD: optional chaining
let city = p.address?.city // String?
print(city ?? "no address")
// BAD: assuming the middle isn't nil
// p.address!.city // may crash
// multi-level chain:
// p.address?.city.count
// assignment via optional chain:
p.address?.city = "Shanghai" // silently fails on nil
// assign address first:
p.address = Address()
p.address?.city = "Shanghai"
print(p.address?.city ?? "")
// method call via optional chain:
// p.address?.method()
// check if any value in chain:
if let c = p.address?.city {
print(c)
}
// common misconception:
// chain returns an optional
// does not auto-unwrap
// vs. forced chain !.

Closure Retain Cycles

A closure capturing self strongly forms a cycle. Use a capture list [weak self].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Loader {
var data: String = ""
var completion: (() -> Void)?
func setup() {
// BAD: closure strongly captures self
// completion = {
// self.data = "loaded"
// }
// GOOD: [weak self]
completion = { [weak self] in
self?.data = "loaded"
// self becomes nil after release
}
}
deinit { print("Loader released") }
}
// use:
var l: Loader? = Loader()
l?.setup()
// run closure or release:
l = nil // releases normally
// other ways to break the cycle:
// [unowned self] assumes existence
// strong reference outside closure, manually nil
// commonly seen in:
// network callbacks, Timer, animations

Array Out of Bounds

An out-of-range index crashes. Access safely with first/last/prefix. Check bounds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let nums = [1, 2, 3]
// BAD: out-of-bounds index
// print(nums[5]) // crash
// GOOD: safe access
if nums.indices.contains(5) {
print(nums[5])
}
// safe methods:
let first = nums.first ?? 0
let last = nums.last ?? 0
// safe element extension:
extension Array {
func safe(_ index: Int) -> Element? {
indices.contains(index) ? self[index] : nil
}
}
print(nums.safe(1) ?? 0) // 2
print(nums.safe(9) ?? 0) // 0
// empty array caution:
// nums[0] also crashes
// iterate with indices or enumerated
// check dynamic indices first

mutating

A struct method that modifies properties needs mutating. Value types are immutable copies.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct Counter {
var count = 0
// Needs to modify itself:
mutating func increment() {
count += 1
}
// Read-only methods don't need it:
func current() -> Int {
count
}
}
var c = Counter()
c.increment()
c.increment()
print(c.current()) // 2
// BAD: calling mutating on a constant instance
// let cc = Counter()
// cc.increment() // error
// GOOD: var instance
var cc2 = Counter()
cc2.increment()
// Reason:
// mutating modifies self
// constant instances are immutable
// Rules:
// struct/enum need mutating
// class does not
// also applies to subscripts and protocol methods
// value semantics in action

String Indices

Strings cannot be subscripted by integers. Characters are multi-byte. Use the index methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let s = "Hello Swift"
// BAD: integer subscript
// let c = s[0] // error
// character count is not byte count
// GOOD: index API
let first = s.startIndex
let c = s[first] // H
print(c)
// Offset:
let second = s.index(after: first)
print(s[second])
// Offset by n:
let idx = s.index(s.startIndex, offsetBy: 3)
print(s[idx])
// Convert to a character array:
let chars = Array(s)
print(chars[0]) // H
// Iterate:
for c in s { print(c) }
// Slice:
// s[..<idx]
// multi-byte safe
// String.Index guarantees boundaries

Protocols and Self

Using the Self constraint in protocols. associatedtype. Type erasure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Protocol Self constraint:
protocol EquatableLike {
func isEqualTo(_ other: Self) -> Bool
}
// Conform:
struct Box: EquatableLike {
var value: Int
func isEqualTo(_ other: Box) -> Bool {
self.value == other.value
}
}
// BAD: using a Self-constrained protocol as a type
// func compare(a: EquatableLike, b: EquatableLike) // error
// GOOD: generic constraint
func compare<T: EquatableLike>(_ a: T, _ b: T) -> Bool {
a.isEqualTo(b)
}
// Associated type:
// protocol Container {
// associatedtype Item
// }
// Type erasure:
// AnyEquatable wrapper
// when dynamic polymorphism is needed
// use generics or type erasure

Implicitly Unwrapped Optionals

T! is an implicitly unwrapped optional. Using it uninitialized crashes. Use with care.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// BAD: implicitly unwrapped access before assignment crashes
var name: String! = nil
// Direct use:
// print(name) // crash (not assigned)
// After assignment:
name = "Nick"
print(name) // unwrapped automatically
// Uses:
// IBOutlet connections
// guaranteed to have a value after init
// two-phase initialization
// Risks:
// crashes when accessed before assignment
// loses optional safety
// not recommended for new code
// GOOD: plain optional
var safeName: String?
// Unwrap:
if let n = safeName {
print(n)
}
// If you must:
// access only after init is certain
// or switch to a let constant

14.Concurrency

GCD, async/await, Task and the actor concurrency model.

GCD Basics

DispatchQueue serial/concurrent queues. Global and main queues.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// Main queue:
let main = DispatchQueue.main
// Global concurrent queue:
let background = DispatchQueue.global(qos: .background)
// Create a serial queue:
let serial = DispatchQueue(label: "com.app.serial")
// Create a concurrent queue:
let concurrent = DispatchQueue(
label: "com.app.concurrent", attributes: .concurrent)
// Submit a task:
background.async {
// runs in the background...
print("background task")
}
// Submit synchronously:
// serial.sync { ... }
// Delayed execution:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("after 1 second")
}
// QoS priority:
// .userInteractive / .userInitiated
// .utility / .background

Dispatch Groups

DispatchGroup waits for multiple tasks to finish. Aggregating batched concurrency.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
let group = DispatchGroup()
let queue = DispatchQueue.global()
// Enter the group:
// multiple async tasks
for i in 0..<3 {
group.enter()
queue.async {
print("task \(i) running")
// simulate work
Thread.sleep(forTimeInterval: 0.2)
group.leave()
}
}
// All-done callback:
group.notify(queue: .main) {
print("all tasks finished")
}
// Wait synchronously:
// group.wait()
// Wait with timeout:
// group.wait(timeout: .now() + 5)
// Notes:
// enter and leave must be paired
// thread-safe
// good for parallel downloads / batch processing

async/await

Modern Swift concurrency syntax. Async functions and suspension. Structured concurrency.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// Async function:
func fetchData() async -> String {
// simulate network latency
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "data"
}
// Call:
func load() async {
let data = await fetchData()
print(data)
}
// Async throwing function:
func risky() async throws -> Int {
try await Task.sleep(nanoseconds: 100_000_000)
return 42
}
// Call:
func caller() async {
if let v = try? await risky() {
print(v)
}
}
// Notes:
// async functions can't be called from a sync context
// wrap in Task { }
// converts to and from the old callback style

async let

Runs several async calls concurrently. Binds independent child tasks. Waits in parallel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// Three independent async tasks:
func loadUser() async -> String { "user" }
func loadPosts() async -> String { "posts" }
func loadComments() async -> String { "comments" }
// async let concurrency:
func loadAll() async -> String {
async let user = loadUser()
async let posts = loadPosts()
async let comments = loadComments()
// all run in parallel:
let result = await "\(user) \(posts) \(comments)"
return result
}
// Usage:
// the three tasks run at the same time
// await aggregates all results
// Notes:
// an async let binding must be awaited
// Dependencies:
// use sequential await when dependent
// use async let when independent
// good for parallel calls to independent APIs

Task

Task creates a unit of concurrency. Inherits context. Cancellation and priority.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Foundation
// Create a task:
func run() async {
let task = Task {
try await Task.sleep(nanoseconds: 500_000_000)
return "done"
}
let result = await task.value // String
print(result)
}
// Dispatch to the main thread:
// @MainActor func updateUI()
// Task { @MainActor in ... }
// Cancel a task:
task.cancel()
// Check for cancellation:
try Task.checkCancellation()
// Structured task group:
await withTaskGroup(of: String.self) { group in
for i in 0..<3 {
group.addTask {
"task\(i)"
}
}
for await r in group {
print(r)
}
}
// Priority: Task(priority: .high)

actor

An actor isolates state. Avoids data races. Accessed through async methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Foundation
// Define an actor:
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func getBalance() -> Double {
balance
}
}
// Usage (await required):
func test() async {
let account = BankAccount()
// accessing isolated state needs await:
await account.deposit(100)
let balance = await account.getBalance()
print(balance)
}
// Concurrency safety:
// the compiler guarantees serialized access
// avoids data races
// Comparison:
// classes need manual locking
// actors isolate automatically
// Notes:
// actor methods are async by default
// must be called with await
// good for shared mutable state

Main Thread

UI work must run on the main thread. Hopping between threads. Checking for the main thread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// Check for the main thread:
if Thread.isMainThread {
print("on the main thread")
}
// Back to the main thread:
DispatchQueue.main.async {
// UI update
print("running on the main thread")
}
// Wait for the main thread:
// DispatchQueue.main.sync { }
// Watch out for deadlock:
// the main thread syncing onto itself deadlocks
// Modern Swift:
@MainActor
func updateUI() {
// isolated to the main thread
}
// Call from an async context:
// await MainActor.run { ... }
// Rules:
// UI updates and animation on the main thread
// long-running work in the background
// hop back to the main thread to update

Thread Safety

Protecting mutable state under concurrent access. Locks, queues, atomic operations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Foundation
// Guard with a serial queue:
final class SafeCounter {
private let queue = DispatchQueue(label: "counter")
private var _count = 0
var count: Int {
queue.sync { _count }
}
func increment() {
queue.sync { _count += 1 }
}
}
let counter = SafeCounter()
for _ in 0..<100 {
DispatchQueue.global().async {
counter.increment()
}
}
// Locks:
// NSLock
let lock = NSLock()
lock.lock()
// critical section...
lock.unlock()
// Atomics:
// OSAllocatedUnfairLock
// Modern approach:
// actor isolation is the safest
// read-only constants need no protection
// prefer immutable data

15.Networking

URLSession, HTTP requests, async networking and working with JSON.

URLSession

URLSession makes network requests. Configuration and delegates. Session types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// Default session:
let session = URLSession.shared
// Custom configuration:
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.waitsForConnectivity = true
let custom = URLSession(configuration: config)
// Request:
let url = URL(string: "https://api.example.com")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
// Session types:
// .default / .ephemeral / .background
// ephemeral stores no cache or cookies
// background for background transfers
// Data task:
let task = session.dataTask(with: request)
// Use together with async/await

GET Requests

Making a GET. Fetching data with async/await. Handling the response status.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// Async GET:
func fetchContent() async throws -> String {
let url = URL(string: "https://api.example.com/data")!
let (data, response) = try await URLSession.shared.data(from: url)
// Check the status code:
guard let http = response as? HTTPURLResponse,
http.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return String(data: data, encoding: .utf8) ?? ""
}
// Usage:
func load() async {
do {
let content = try await fetchContent()
print(content)
} catch {
print("request failed: \(error)")
}
}
// Query parameters:
var comps = URLComponents(string: "https://api.example.com/search")!
comps.queryItems = [URLQueryItem(name: "q", value: "swift")]
// Timeout:
// request.timeoutInterval = 10
// Old callback style:
// dataTask(with:completionHandler:)

POST Requests

Sending a JSON body. Form submissions. Setting Content-Type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// POST JSON:
func postJSON() async throws {
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// body:
let payload: [String: Any] = ["name": "Nick", "age": 30]
request.httpBody = try JSONSerialization.data(withJSONObject: payload)
let (data, response) = try await URLSession.shared.data(for: request)
// handle the response...
print(response)
}
// Form:
var form = URLRequest(url: URL(string: "https://x/login")!)
form.httpMethod = "POST"
form.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
form.httpBody = "user=nick&pwd=123".data(using: .utf8)
// Notes:
// stream large bodies
// check the response status code too
// Codable can encode directly

Async Networking

Network calls with async/await. Concurrent requests. Cancelling tasks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// Async request + Codable:
struct User: Codable { var id: Int; var name: String }
func fetchUsers() async throws -> [User] {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([User].self, from: data)
}
// Multiple concurrent requests:
func loadAll() async throws -> (User, [String]) {
async let user = fetchUsers()
async let tags: [String] = ["a", "b"]
return (try await user.first!, try await tags)
}
// Task cancellation:
// cancelling a Task aborts the network request
// Response check
func fetchWithTimeout() async throws -> Data {
let url = URL(string: "https://api.example.com")!
// Timeout:
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForRequest = 10
let session = URLSession(configuration: config)
return try await session.data(from: url).0
}
// Error handling:
// URLError categorizes the failure

URL Building

URLComponents for safe URL construction. Query-parameter encoding. Path composition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// Base URL:
var comps = URLComponents()
comps.scheme = "https"
comps.host = "api.example.com"
comps.path = "/v1/search"
// Query parameters:
comps.queryItems = [
URLQueryItem(name: "q", value: "swift concurrency"),
URLQueryItem(name: "limit", value: "10"),
URLQueryItem(name: "page", value: "2"),
]
// Automatic encoding:
let url = comps.url!
print(url.absoluteString)
// Parse a URL:
if let c = URLComponents(string: url.absoluteString) {
print(c.host ?? "")
for item in c.queryItems ?? [] {
print("\(item.name)=\(item.value ?? "")")
}
}
// Benefits:
// automatic percent-encoding
// safe for Unicode / special characters
// avoids string-concatenation mistakes
// Add a fragment:
comps.fragment = "section"

Downloading Files

Download tasks save files. Progress monitoring. Background downloads.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// Download data:
func downloadImage() async throws -> Data {
let url = URL(string: "https://example.com/img.png")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// Save to a file:
func save() async throws {
let data = try await downloadImage()
let dir = FileManager.default.temporaryDirectory
let file = dir.appendingPathComponent("img.png")
try data.write(to: file)
print(file.path)
}
// File download task:
let config = URLSessionConfiguration.background(withIdentifier: "dl")
let session = URLSession(configuration: config)
// Progress observing:
// delegate URLSessionDownloadDelegate
// download-completion callback
// resumable downloads
// background supported
// Notes:
// write large files to disk
// pick the right sandbox directory
// mind memory usage

Network JSON

Request JSON and decode with Codable. Date and strategy handling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// Model:
struct Post: Codable {
var id: Int
var title: String
var created: Date
}
// Date decoding strategy:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// Snake case:
// decoder.keyDecodingStrategy = .convertFromSnakeCase
// Request:
func fetchPosts() async throws -> [Post] {
let url = URL(string: "https://api.example.com/posts")!
let (data, _) = try await URLSession.shared.data(from: url)
return try decoder.decode([Post].self, from: data)
}
// Encode for upload:
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// Error handling:
// DecodingError categories
// .dataCorrupted / .keyNotFound
// handle network-layer errors separately
// validate the model

Uploading Files

Multipart form upload. File body. Boundary separator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// multipart upload:
func upload(data: Data, filename: String) async throws {
let url = URL(string: "https://api.example.com/upload")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue(
"multipart/form-data; boundary=\(boundary)",
forHTTPHeaderField: "Content-Type")
// Build the body:
var body = Data()
body.append("--\(boundary)\r\n".data(using: .utf8)!)
let header = "Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n\r\n"
body.append(header.data(using: .utf8)!)
body.append(data)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = body
let (_, response) = try await URLSession.shared.data(for: request)
print(response)
}
// Notes:
// boundary must be unique
// stream large files
// use URLSessionUploadTask for progress
// validate the server response

16.Date and Time

Date, DateFormatter, calendar arithmetic, and timers.

Date

Date represents a point in time. Timezone-independent absolute instant. Creation and comparison.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// Current time:
let now = Date()
// Timestamp (seconds):
let ts = now.timeIntervalSince1970
print(ts)
// Create from a timestamp:
let fromTs = Date(timeIntervalSince1970: 1700000000)
// Create with an offset:
let inOneHour = Date().addingTimeInterval(3600)
// Compare:
let earlier = Date(timeIntervalSince1970: 0)
print(now > earlier) // true
// Time interval:
let diff = now.timeIntervalSince(earlier)
print(diff) // seconds
// Sort:
let dates = [now, earlier].sorted()
// Notes:
// Date is locale-independent
// use DateFormatter for display
// use Calendar for arithmetic
// don't add or subtract seconds by hand

Date Formatting

DateFormatter displays dates. Localized formats. Custom formats.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
let now = Date()
let formatter = DateFormatter()
// Localization:
formatter.locale = Locale(identifier: "zh_CN")
formatter.timeZone = .current
// Preset styles:
formatter.dateStyle = .medium // medium date style
formatter.timeStyle = .short // short time style
print(formatter.string(from: now))
// Custom format:
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(formatter.string(from: now))
// Format symbols:
// yyyy year / MM month / dd day
// HH 24-hour / mm minute / ss second
// EEEE full weekday name
formatter.dateFormat = "yyyy-MM-dd EEEE"
print(formatter.string(from: now))
// Notes:
// set locale for fixed formats
// prefer preset styles for display
// 12/24-hour clock

Parsing Dates

Convert strings to Date. Fixed-format parsing. ISO8601 parsing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// Parse a custom format:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = .current
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date = formatter.date(from: "2026-08-02 10:30:00") {
print(date)
}
// ISO8601 parsing:
let iso = ISO8601DateFormatter()
if let d = iso.date(from: "2026-08-02T10:30:00Z") {
print(d)
}
// Loose parsing:
formatter.dateFormat = "yyyy/M/d"
// Notes:
// parsing failure returns nil
// use en_US_POSIX for fixed formats
// avoid ambiguity
// when input formats vary
// try several formatters
// prefer iso8601 for server ISO dates

Date Components

DateComponents extracts year, month, and day. Calendar component arithmetic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
let calendar = Calendar.current
let now = Date()
// Extract components:
let comps = calendar.dateComponents(
[.year, .month, .day, .weekday], from: now)
print(comps.year ?? 0, comps.month ?? 0, comps.day ?? 0)
print(comps.weekday ?? 0) // 1=Sunday...7=Saturday
// Extract a single one:
let year = calendar.component(.year, from: now)
// Build a date from components:
var c = DateComponents()
c.year = 2026
c.month = 8
c.day = 2
let date = calendar.date(from: c)
print(date ?? Date())
// Start of today:
let start = calendar.startOfDay(for: now)
// Notes:
// components follow calendar semantics
// safe across time zones
// replaces manual date math

Calendar Arithmetic

Add or subtract days and months from a date. Next week / next month. Safe Calendar arithmetic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
let calendar = Calendar.current
let now = Date()
// Add days:
let tomorrow = calendar.date(byAdding: .day, value: 1, to: now)!
// Add months:
let nextMonth = calendar.date(byAdding: .month, value: 1, to: now)!
// Add years:
let nextYear = calendar.date(byAdding: .year, value: 1, to: now)!
// Start of month:
let monthStart = calendar.date(from:
calendar.dateComponents([.year, .month], from: now))!
// Next Monday:
let monday = calendar.nextDate(
after: now, matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime)!
// Date difference:
let days = calendar.dateComponents(
[.day], from: now, to: tomorrow).day!
print(days) // 1
// Same day check:
calendar.isDate(now, inSameDayAs: tomorrow)
// Time zone:
// calendar.timeZone

Time Intervals

Measure code execution time. TimeInterval is in seconds. Performance timing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// Timing:
let start = Date()
// run the task...
Thread.sleep(forTimeInterval: 0.3)
let elapsed = Date().timeIntervalSince(start)
print("\(elapsed) seconds") // ~0.3
// High-precision timing:
let t0 = DispatchTime.now()
// task...
let t1 = DispatchTime.now()
let nanos = t1.uptimeNanoseconds - t0.uptimeNanoseconds
print("\(Double(nanos) / 1e6) ms")
// Sleep:
Thread.sleep(forTimeInterval: 0.5)
// Async sleep:
// try await Task.sleep(nanoseconds: 500_000_000)
// TimeInterval = Double seconds
// Notes:
// warm up and ignore the first run
// average over several runs
// keep seconds as the unit

Timer

Timers for repeated execution. One-shot timers. RunLoop caveats.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// Repeating timer:
var count = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
count += 1
print("tick \(count)")
if count >= 3 {
timer.invalidate() // stop
}
}
// One-shot:
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) {
print("runs after 2 seconds")
}
// Notes:
// scheduledTimer joins the current RunLoop
// works on the main thread
// background threads need a run loop
// Invalidate:
// timer.invalidate()
// Avoid strong references:
// closures capturing self create cycles
// use [weak self]
// Modern alternative:
// a Task.sleep loop

Date Comparison

Compare dates chronologically. Test if they fall on the same day. Sorting and validation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
let calendar = Calendar.current
let now = Date()
let past = Date(timeIntervalSinceNow: -3600)
let future = Date(timeIntervalSinceNow: 3600)
// Direct comparison:
print(future > now) // true
// Same day:
calendar.isDate(now, inSameDayAs: future)
// Today/yesterday/tomorrow:
calendar.isDateInToday(now)
calendar.isDateInYesterday(past)
calendar.isDateInTomorrow(future)
// Range contains:
let range = past...future
print(range.contains(now)) // true
// Sort:
let dates = [future, past, now].sorted()
// Days between:
let daysBetween = calendar.dateComponents(
[.day], from: past, to: future).day ?? 0
print(daysBetween)
// Check expiration:
if let expires = Calendar.current.date(byAdding: .hour, value: 2, to: now),
now > expires {
print("expired")
}

17.Process and System

Command-line arguments, environment variables, process operations, and file paths.

Command-line arguments

CommandLine retrieves arguments. Handle arguments and options.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// Get arguments:
let args = CommandLine.arguments
print(args) // ["program", "arg1", "arg2"]
// Argument count:
let count = CommandLine.arguments.count
// Simple parsing:
let arguments = CommandLine.arguments
var verbose = false
var inputFile = ""
var index = 1
while index < arguments.count {
switch arguments[index] {
case "-v", "--verbose":
verbose = true
case "-f", "--file":
index += 1
inputFile = arguments[index]
default:
print("unknown argument: \(arguments[index])")
}
index += 1
}
print("verbose=\(verbose), file=\(inputFile)")
// Notes:
// the first one is the program path
// use CommandLine.arguments for arguments
// use the ArgumentParser library in production

Environment Variables

Read environment variables. Set and pass them. Caveats for sensitive data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// Read an environment variable:
if let home = ProcessInfo.processInfo.environment["HOME"] {
print(home)
}
// Check existence:
let hasKey = ProcessInfo.processInfo.environment["API_KEY"] != nil
// All variables:
for (key, value) in ProcessInfo.processInfo.environment {
print("\(key)=\(value)")
}
// Default value:
let port = ProcessInfo.processInfo.environment["PORT"] ?? "8080"
print(port)
// System info:
let os = ProcessInfo.processInfo.operatingSystemVersionString
let cores = ProcessInfo.processInfo.activeProcessorCount
print(os, cores)
// Security notes:
// keep secrets in environment variables
// don't commit them to the repo
// avoid printing sensitive values

Process

Process launches child processes. Execute commands. Capture output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// Run a command:
func run(_ cmd: String, _ args: [String]) throws -> String {
let process = Process()
process.executableURL = URL(fileURLWithPath: cmd)
process.arguments = args
// Capture output:
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8) ?? ""
}
// Usage:
try print(run("/usr/bin/env", ["date"]))
// Environment variables:
process.environment = ["PATH": "/usr/bin"]
// Working directory:
process.currentDirectoryURL = URL(fileURLWithPath: "/tmp")
// Exit code:
let status = process.terminationStatus
// Notes:
// the path must be correct
// split long commands into strings
// use terminationHandler for async

Path Handling

URL path manipulation. Composition, extensions, filenames. Sandbox directories.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
let fileURL = URL(fileURLWithPath: "/tmp/data/file.txt")
// File name:
let name = fileURL.lastPathComponent // "file.txt"
// Drop the extension:
let base = fileURL.deletingPathExtension().lastPathComponent // "file"
// Extension:
let ext = fileURL.pathExtension // "txt"
// Parent directory:
let dir = fileURL.deletingLastPathComponent
// Append a component:
let newFile = dir.appendingPathComponent("out.txt")
// Append an extension:
let md = fileURL.appendingPathExtension("md")
// Sandbox directories:
let docs = FileManager.default.urls(
for: .documentDirectory, in: .userDomainMask)[0]
let caches = FileManager.default.temporaryDirectory
// Directory check:
// fileURL.hasDirectoryPath
// Relative to absolute:
// URL(fileURLWithPath: "a.txt").standardizedFileURL

Exit Codes

Program exit code. exit and fatalError. 0 means success, non-zero means failure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// Normal exit:
exit(0)
// Failure exit:
// exit(1)
// With an error message:
// write to stderr
// FileHandle.standardError.write(
// Data("error\n".utf8))
// exit(2)
// Common conventions:
// 0 success
// 1 general error
// 2 usage error
// 127 command not found
// Check a child process status:
let process = Process()
// process.waitUntilExit()
// let code = process.terminationStatus
// print(code)
// Notes:
// exit terminates immediately
// defer does not run
// handy in scripts
// don't call exit on normal completion
// return Int from main

Signal Handling

Catch system signals. SIGINT and others. Graceful exit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
import Darwin
// Signal handling:
signal(SIGINT) { _ in
print("received Ctrl-C")
exit(0)
}
// Ignore a signal:
// signal(SIGHUP, SIG_IGN)
// Signal list:
// SIGINT interrupt (2)
// SIGTERM terminate (15)
// SIGKILL force kill (9) cannot be caught
// Graceful shutdown pattern:
var running = true
signal(SIGTERM) { _ in
running = false
}
// while running {
// // main loop...
// sleep(1)
// }
// Notes:
// keep signal handlers simple
// communicate via atomic variables
// DispatchSource is more modern:
// DispatchSource.makeSignalSource
// abrupt exit is discouraged

Working Directory

Get and switch the current working directory. Base for relative file paths.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// Current working directory:
let cwd = FileManager.default.currentDirectoryPath
print(cwd)
// Change directory:
// FileManager.default.changeCurrentDirectoryPath("/tmp")
// Relative path resolution:
let rel = URL(fileURLWithPath: "data/config.json")
print(rel.path) // based on the current directory
// Standardize:
let normalized = rel.standardizedFileURL
// Path check:
let isAbsolute = rel.isFileURL
// Common locations:
let home = FileManager.default.homeDirectoryForCurrentUser
let temp = FileManager.default.temporaryDirectory
// Notes:
// the working directory affects relative paths
// sandboxed environments differ
// GUI apps beware:
// the working directory may be root
// building absolute paths explicitly is safer
// use a relative base for config

Process File I/O

Read standard input. Write to standard output. Pipe interaction.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// Read standard input:
if let line = readLine() {
print("received: \(line)")
}
// Read in a loop:
// while let line = readLine() {
// print(line)
// }
// Standard output:
print("goes to stdout")
// Standard error:
FileHandle.standardError.write(
Data("error message\n".utf8))
// Redirect from a file:
// swift main.swift < input.txt
// Pipe input:
// echo "data" | swift main.swift
// Redirect output:
// swift main.swift > out.txt 2>&1
// Notes:
// readLine returns nil at EOF
// mind performance on large input
// use FileHandle.standardInput for binary

18.Regular Expressions

Regex literals, NSRegularExpression, and pattern matching.

Regex Basics

Regex syntax concepts. Character classes, quantifiers, groups.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// Basic syntax:
// Literal: /abc/ matches abc
// Character class: [abc] [0-9] \\d
// Quantifiers: * + ? {n,m}
// Anchors: ^ $ \\b
// Group: (...) captures
// Or: a|b
// Swift 5.7+ regex literals:
let pattern = /\\d{4}-\\d{2}-\\d{2}/
let text = "date 2026-08-02 end"
if let match = text.firstMatch(of: pattern) {
print(match.0) // 2026-08-02
}
// Common:
// \\d digit \\w word \\s whitespace
// \\D \\W \\S negated
// Greedy vs lazy:
// a.*b greedy / a.*?b lazy
// Escape: \\\\. matches a dot

Regex Literals

Swift 5.7 regex literals /.../. Typed matching. Named captures.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// Regex literal:
let pattern = /h(\\d+)x/
// Typed captures:
let text = "h123x h99x"
if let match = text.firstMatch(of: pattern) {
print(match.0) // h123x
print(match.1) // 123 (Substring)
}
// Named captures:
let named = /(?<code>\\d{3})-(?<area>\\d{4})/
if let m = "123-4567".firstMatch(of: named) {
print(m.code) // 123
print(m.area) // 4567
}
// Options:
let caseInsensitive = /abc/ .ignoresCase()
// Find all:
for m in text.matches(of: pattern) {
print(m.0)
}
// Notes:
// type-safe, validated at compile time
// converts to and from NSRegularExpression
// requires macOS 13 / iOS 16+

NSRegularExpression

The NSRegularExpression regex engine. Range matching. Compatible with older systems.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// Create:
let pattern = "\\d+"
let regex = try! NSRegularExpression(pattern: pattern)
// Match:
let text = "price 100 usd, discount 20 usd"
let range = NSRange(text.startIndex..., in: text)
// All matches:
let matches = regex.matches(in: text, range: range)
for m in matches {
if let r = Range(m.range, in: text) {
print(text[r])
}
}
// Capture groups:
let groupRegex = try! NSRegularExpression(pattern: "(\\d+)-(\\d+)")
// First match:
if let first = groupRegex.firstMatch(in: text, range: range) {
// first.range(at: 1) is group 1
}
// Replace:
let replaced = regex.stringByReplacingMatches(
in: text, range: range, withTemplate: "#")
print(replaced)
// Options:
// .caseInsensitive / .anchorsMatchLines

Matching

First match, all matches, test if it matches. Retrieve ranges.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// Test for a match:
let email = "[email protected]"
// Whole match:
let full = /[a-z]+@[a-z]+\\.[a-z]+/
print(email.wholeMatch(of: full) != nil)
// Partial containment:
print(email.contains(/@/))
// First match:
let pattern = /\\d+/
if let m = "abc123def".firstMatch(of: pattern) {
print(m.0) // 123
}
// All matches:
let all = "a1b2c3".matches(of: /\\d+/)
print(all.count) // 3
// Match position:
// m.range returns the range
// Prefix match:
if "2026-08".hasPrefix("2026") {}
// Notes:
// wholeMatch matches the whole string
// firstMatch the first one
// matches all of them
// beware of empty matches

Capture Groups

Extract capture group contents. Named captures. Regex group references.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// Capture groups:
let pattern = /(\\d{4})-(\\d{2})-(\\d{2})/
let text = "2026-08-02"
if let m = text.wholeMatch(of: pattern) {
print(m.0) // 2026-08-02
print(m.1) // 2026
print(m.2) // 08
print(m.3) // 02
}
// Named captures:
let named = /(?<year>\\d{4})-(?<month>\\d{2})/
if let m = "2026-08".wholeMatch(of: named) {
print(m.year) // 2026
print(m.month) // 08
}
// Optional capture:
// (x)? optional if matched
// Non-capturing group:
// (?:x) does not capture
// NSRegularExpression:
// m.range(at: 1) extracts a group
// Uses:
// parsing structured text
// extracting key=value

Replacement

Regex text replacement. Templates reference capture groups. Conditional replacement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// Literal replacement:
let s = "Hello, world!"
let replaced = s.replacingOccurrences(of: "o", with: "0")
print(replaced) // Hell0, w0rld!
// Regex replacement:
let text = "date: 2026-08-02"
let regex = try! NSRegularExpression(pattern: "(\\d{4})-(\\d{2})-(\\d{2})")
let nsRange = NSRange(text.startIndex..., in: text)
// Template group references:
let formatted = regex.stringByReplacingMatches(
in: text, range: nsRange,
withTemplate: "$2/$3/$1")
print(formatted) // date: 08/02/2026
// Replacement scope:
let trimmed = text.replacingOccurrences(
of: "\\s+", with: " ", options: .regularExpression)
// Template special characters:
// $0 whole match $1 group 1
// conditional replacement needs a match loop

Splitting

Split a string by regex. Keep or drop the delimiter. Multiple delimiters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// String split:
let csv = "a,b,c"
let parts = csv.split(separator: ",")
print(parts) // ["a", "b", "c"]
// Regex split:
let text = "one two three"
let words = text.components(separatedBy: .whitespacesAndNewlines)
// Multiple separators:
let messy = "a;b,c|d"
let regex = try! NSRegularExpression(pattern: "[;,_|]+")
let splitParts = regex.split(messy)
// Keep empty strings:
// split drops empties by default
// omittingEmptySubsequences
// Split by line:
let lines = text.components(separatedBy: .newlines)
// Notes:
// separators as regex
// separators are removed when splitting
// mind performance on large text

Common Patterns

Common regex templates for email, URL, numbers, phone numbers, and more.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// Email:
let email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/
// URL:
let url = /https?:\\/\\/[\\w.-]+(?:\\/[\\w._-]*)*/
// Integer:
let int = /^[+-]?\\d+$/
// Float:
let float = /^[+-]?\\d+\\.\\d+$/
// Mobile phone number:
let phone = /^1[3-9]\\d{9}$/
// ID card number:
let idCard = /^\\d{17}[0-9Xx]$/
// IP address:
let ip = /(?:\\d{1,3}\\.){3}\\d{1,3}/
// Chinese characters:
let chinese = /[\\u4e00-\\u9fa5]+/
// Whitespace:
let spaces = /\\s+/
// Usage:
print("[email protected]".wholeMatch(of: email) != nil)
print("13800138000".wholeMatch(of: phone) != nil)
// Notes:
// regexes must be tested
// performance: be careful with complex patterns
// use a dedicated library for real validation

19.Build and Tooling

swiftc compilation, SwiftPM build, formatting, and linting tools.

swiftc Compilation

Compile a single file from the command line. Generate an executable. Optimization options.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Compile an executable:
# swiftc main.swift -o app
# Run:
# ./app
# Interpret directly:
# swift main.swift
# Multiple source files:
# swiftc main.swift utils.swift -o app
# Emit a module:
# swiftc -emit-module
# Optimization:
# -O optimized build
# -Ounchecked faster but unchecked
# -Onone debug
# Debug info:
# -g emit debug symbols
# Link a framework:
# -framework Foundation
# Cross-compile:
# -target x86_64-unknown-linux-gnu
# Check the version:
# swift --version
# Notes:
# SwiftPM manages multiple files
# use swiftc for single-file development

SwiftPM Build

swift build compiles a project. Incremental builds. Release mode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Initialize a project:
# swift package init --type executable
# Build:
# swift build
# Release mode:
# swift build -c release
# Specify a product:
# swift build --product app
# Incremental build (default)
# Clean:
# swift package clean
# Update dependencies:
# swift package resolve
# Show the dependency tree:
# swift package show-dependencies
# Output location:
# .build/debug/ and .build/release/
# Run the executable:
# .build/debug/myapp
# Notes:
# Package.swift declares the package
# dependencies use a git URL/version
# build products live in .build/

Running Tests

swift test runs the test suite. XCTest and test targets.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Run tests:
# swift test
# Select tests:
# swift test --filter TestMath
# A single case:
# swift test --filter TestMath/testAdd
# Parallel:
# swift test --parallel
# Code coverage:
# swift test --enable-code-coverage
# Generate a coverage report:
# llvm-cov report .build/debug/*.xctest
# Test file:
import XCTest
final class TestMath: XCTestCase {
func testAdd() {
XCTAssertEqual(add(1, 2), 3)
}
}
# Assertions:
# XCTAssertTrue / XCTAssertNil
# Async tests:
# XCTestExpectation
# Notes:
# test targets are declared in Package.swift
# testXxx names are auto-discovered

Package.swift

The SwiftPM package manifest. Targets and dependencies. Platform configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyApp",
// Platforms:
platforms: [
.macOS(.v13)
],
// Products:
products: [
.executable(name: "app", targets: ["App"]),
.library(name: "Core", targets: ["Core"]),
],
// Dependencies:
dependencies: [
.package(url: "https://github.com/xxx/yyy", from: "1.0.0"),
],
// Targets:
targets: [
.executableTarget(
name: "App",
dependencies: ["Core", "yyy"]),
.target(name: "Core"),
.testTarget(
name: "AppTests",
dependencies: ["App"]),
]
)
# Dependency versions:
# from: 1.0.0 up-to-next-major
# exact: 1.2.3 exact
# branch: main

SwiftLint

Code style checking. Configure rules. CI integration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Install (Homebrew):
# brew install swiftlint
# Run:
# swiftlint
# Fix:
# swiftlint autocorrect
# Specify a directory:
# swiftlint lint --path Sources/
# Config file:
# generate the default config
# swiftlint generate-docs
# Common rules:
# line_length line length
# trailing_whitespace trailing whitespace
# force_cast force cast
# force_unwrapping force unwrap
# Configure .swiftlint.yml:
# disabled_rules:
# - force_cast
# opt_in_rules:
# - empty_count
# Exclude files:
# excluded:
# - Generated/
# CI integration:
# a failure fails the build
# keeps style consistent

swift-format

The official code formatter. Formatting rules and configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Format:
# swift-format format -i Sources/*.swift
# Check:
# swift-format lint -r Sources/
# Configure:
# swift-format dump-configuration > .swift-format
# Integrates with Xcode
# Example config:
# {
# "indentation": {
# "spaces": 4
# },
# "lineBreakBeforeEachArgument": false,
# "rules": {
# "AllPublicDeclarationsHaveDocumentation": false
# }
# }
# How to use:
# format on save in the editor
# pre-commit hook
# CI format check
# Difference from SwiftLint:
# format handles formatting
# lint handles style rules
# the two work together

xcodebuild

Command-line build for Xcode projects. Export and archive. CI usage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# List projects/workspaces:
# xcodebuild -list
# Build:
# xcodebuild -project App.xcodeproj \
# -scheme App -configuration Debug build
# Workspace build:
# xcodebuild -workspace App.xcworkspace \
# -scheme App build
# Device SDK:
# -sdk iphoneos
# Simulator:
# -destination 'platform=iOS Simulator,name=iPhone 15'
# Test:
# xcodebuild test -scheme App
# Archive:
# xcodebuild archive -scheme App \
# -archivePath build/App.xcarchive
# Export IPA:
# xcodebuild -exportArchive \
# -exportOptionsPlist ExportOptions.plist
# Common in CI:
# Analyze the output with xcresulttool

Release Process

Version control, build & release, distribution. Release checklist.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Version number convention:
# MAJOR.MINOR.PATCH
# 1.0.0 initial
# 2.1.0 new features
# 2.1.3 bug fixes
# Git tagging:
# git tag v2.1.0
# git push --tags
# Release process:
# 1. Bump version number
# 2. Update CHANGELOG
# 3. All tests pass
# 4. Build release configuration
# 5. Generate archive
# 6. Sign and notarize
# 7. Upload and distribute
# CLI tool distribution:
# Static compilation
# tar packaging
# Homebrew formula
# Checklist:
# Test coverage
# Documentation updated
# Dependencies locked
# Artifact verification

Official Links

Direct links to the official docs and resources.

About this Cheatsheet

This page is a self-contained cheatsheet for Swift 5.9, covering the language core and the most common uses of the standard library in real projects — about 80% of everyday use. Content leans toward modern idioms: value types (struct/enum), protocol-oriented design, async/await structured concurrency, if let pattern matching, and Codable serialization. For authoritative references, see the official Swift Language Guide and API Design Guidelines. 19 chapters, each focused on one topic — from your first program to optionals, protocols, concurrency, and common pitfalls. Each chapter is broken into 8 example-driven subsections (5–20 lines each), totalling about 150 topics. Code snippets are deliberately short and self-explanatory. All processing happens in the browser — no uploads, no tracking. This page is part of GuruToolkit's free developer toolkit; code snippets are free to use with no warranty of any kind.

Version 2.1.0