Getting started
Hello World
print("Hello, World!")
Variables & Constants
var variable = 42 // Mutable
let constant = 42 // Immutable (preferred)
var explicit: Int = 42 // Explicit type
Type Inference
let inferredInt = 42 // Int
let inferredDouble = 3.14 // Double
let inferredString = "Hello" // String
let inferredBool = true // Bool
String Interpolation
let name = "World"
print("Hello, \(name)!") // Hello, World!
let x = 3, y = 5
print("\(x) × \(y) = \(x * y)") // 3 × 5 = 15
Basic Types
Numbers
let integer: Int = 42
let float: Float = 3.14
let double: Double = 3.14159
let hex: Int = 0xFF // 255
let binary: Int = 0b1010 // 10
let octal: Int = 0o21 // 17
Strings
let str = "Hello"
let multi = """
Multiple
lines
"""
str.isEmpty // false
str.count // 5
str.uppercased() // "HELLO"
str.lowercased() // "hello"
String Operations
let hello = "Hello"
let world = "World"
hello + " " + world // "Hello World"
hello.contains("ell") // true
hello.hasPrefix("He") // true
hello.hasSuffix("lo") // true
Booleans
let isTrue = true
let isFalse = false
!isTrue // false
isTrue && isFalse // false
isTrue || isFalse // true
Optionals
Declaration
var optionalInt: Int? = nil
optionalInt = 42
var implicitOptional: Int! = 42 // ⚠️ Implicitly unwrapped
Safe Unwrapping
// Optional binding
if let value = optionalInt {
print(value) // Safe access
}
// Guard statement
guard let value = optionalInt else {
return
}
print(value) // Available in scope
// Nil coalescing
let result = optionalInt ?? 0 // Use 0 if nil
Optional Chaining
class Person {
var name: String?
}
let person: Person? = Person()
let upperName = person?.name?.uppercased()
// Returns String? (nil if any step is nil)
Force Unwrapping
let value = optionalInt! // ⚠️ Crashes if nil
Collections
Arrays
var numbers = [1, 2, 3]
var empty: [Int] = []
var explicit = Array<Int>()
numbers.append(4) // [1, 2, 3, 4]
numbers.insert(0, at: 0) // [0, 1, 2, 3, 4]
numbers.remove(at: 0) // [1, 2, 3, 4]
numbers[0] // 1
numbers.count // 4
numbers.isEmpty // false
numbers.first // Optional(1)
numbers.last // Optional(4)
Dictionaries
var dict = ["key": "value"]
var empty: [String: Int] = [:]
dict["newKey"] = "newValue"
dict["key"] // Optional("value")
dict["missing"] // nil
dict.removeValue(forKey: "key")
dict.count // Count of pairs
dict.keys // Keys collection
dict.values // Values collection
Sets
var set: Set = [1, 2, 3]
var empty = Set<Int>()
set.insert(4)
set.remove(1)
set.contains(2) // true
set.count // 3
// Set operations
let a: Set = [1, 2, 3]
let b: Set = [3, 4, 5]
a.union(b) // {1, 2, 3, 4, 5}
a.intersection(b) // {3}
a.subtracting(b) // {1, 2}
Tuples
let tuple = (1, "two", 3.0)
tuple.0 // 1
tuple.1 // "two"
let named = (x: 1, y: 2)
named.x // 1
let (a, b) = (1, 2) // Decomposition
let (x, _) = (1, 2) // Ignore with _
Control Flow
If Statements
if condition {
// code
} else if otherCondition {
// code
} else {
// code
}
// Inline if (ternary)
let result = condition ? "yes" : "no"
Switch Statements
switch value {
case 0:
print("zero")
case 1, 2: // Multiple values
print("one or two")
case 3...5: // Range
print("three to five")
case let x where x > 5: // Pattern + condition
print("greater than 5")
default:
print("other")
}
// ⚠️ No implicit fallthrough (no break needed)
For Loops
for i in 0..<5 { // 0 to 4
print(i)
}
for i in 0...5 { // 0 to 5 (inclusive)
print(i)
}
for item in array {
print(item)
}
for (key, value) in dictionary {
print("\(key): \(value)")
}
for _ in 1...3 { // Ignore iterator
print("repeat")
}
While Loops
while condition {
// code
}
repeat {
// code
} while condition // Do-while equivalent
Loop Control
for i in 0..<10 {
if i == 5 { break } // Exit loop
if i % 2 == 0 { continue } // Skip iteration
}
// Labeled statements
outer: for i in 0..<3 {
for j in 0..<3 {
if j == 2 { break outer }
}
}
Functions
Basic Functions
func greet(name: String) -> String {
return "Hello, \(name)!"
}
greet(name: "World") // "Hello, World!"
// Implicit return (single expression)
func add(a: Int, b: Int) -> Int {
a + b
}
Multiple Parameters
func greet(person: String, from city: String) -> String {
"Hello \(person) from \(city)!"
}
// External and internal parameter names
greet(person: "Alice", from: "NYC")
Default Parameters
func greet(name: String = "World") -> String {
"Hello, \(name)!"
}
greet() // "Hello, World!"
greet(name: "Alice") // "Hello, Alice!"
Variadic Parameters
func sum(_ numbers: Int...) -> Int {
numbers.reduce(0, +)
}
sum(1, 2, 3, 4) // 10
Inout Parameters
func increment(_ value: inout Int) {
value += 1
}
var number = 5
increment(&number) // number is now 6
// ⚠️ Requires & when calling
Function Types
func add(a: Int, b: Int) -> Int {
a + b
}
let operation: (Int, Int) -> Int = add
operation(2, 3) // 5
// Function as parameter
func calculate(a: Int, b: Int, op: (Int, Int) -> Int) -> Int {
op(a, b)
}
calculate(a: 5, b: 3, op: add) // 8
Closures
Basic Syntax
let greet = { (name: String) -> String in
return "Hello, \(name)!"
}
greet("World") // "Hello, World!"
Shorthand Syntax
let numbers = [1, 2, 3, 4, 5]
// Full closure
numbers.map({ (n: Int) -> Int in
return n * 2
})
// Type inference
numbers.map({ n in
return n * 2
})
// Implicit return
numbers.map({ n in n * 2 })
// Shorthand argument names
numbers.map({ $0 * 2 })
// Trailing closure
numbers.map() { $0 * 2 }
// Omit parentheses
numbers.map { $0 * 2 }
Capturing Values
func makeIncrementer(increment: Int) -> () -> Int {
var total = 0
return {
total += increment
return total
}
}
let inc = makeIncrementer(increment: 5)
inc() // 5
inc() // 10
inc() // 15
Escaping Closures
var handlers: [() -> Void] = []
func addHandler(handler: @escaping () -> Void) {
handlers.append(handler)
}
// ⚠️ @escaping required when closure outlives function
Structs & Classes
Structs
struct Point {
var x: Int
var y: Int
// Methods
func distance() -> Double {
sqrt(Double(x * x + y * y))
}
// Mutating methods
mutating func moveBy(x deltaX: Int, y deltaY: Int) {
x += deltaX
y += deltaY
}
}
var point = Point(x: 0, y: 0) // Auto-generated initializer
point.moveBy(x: 5, y: 5)
Classes
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func greet() -> String {
"Hi, I'm \(name)"
}
}
let person = Person(name: "Alice", age: 30)
Computed Properties
struct Rectangle {
var width: Double
var height: Double
var area: Double {
get { width * height }
set { width = sqrt(newValue) }
}
// Read-only (no set)
var perimeter: Double {
2 * (width + height)
}
}
Property Observers
class StepCounter {
var steps: Int = 0 {
willSet {
print("About to set to \(newValue)")
}
didSet {
print("Changed from \(oldValue) to \(steps)")
}
}
}
Static Properties
struct Math {
static let pi = 3.14159
static func square(_ x: Int) -> Int {
x * x
}
}
Math.pi // 3.14159
Math.square(5) // 25
Inheritance
class Animal {
func makeSound() {
print("Some sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Woof!")
}
}
// Prevent overriding
final class Cat: Animal { }
Enumerations
Basic Enums
enum Direction {
case north
case south
case east
case west
}
// Shorthand
enum Direction {
case north, south, east, west
}
var dir = Direction.north
dir = .south // Type inferred
Raw Values
enum Planet: Int {
case mercury = 1
case venus = 2
case earth = 3
}
Planet.earth.rawValue // 3
Planet(rawValue: 2) // Optional(Planet.venus)
// String raw values
enum Color: String {
case red = "RED"
case green = "GREEN"
}
Color.red.rawValue // "RED"
Associated Values
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var code = Barcode.upc(8, 85909, 51226, 3)
code = .qrCode("ABCDEF")
// Extracting values
switch code {
case .upc(let system, let manufacturer, let product, let check):
print("UPC: \(system)-\(manufacturer)-\(product)-\(check)")
case .qrCode(let productCode):
print("QR: \(productCode)")
}
Recursive Enums
indirect enum Tree {
case leaf(Int)
case node(Tree, Tree)
}
let tree = Tree.node(
.leaf(1),
.node(.leaf(2), .leaf(3))
)
// ⚠️ indirect required for recursive types
Enum Methods
enum Status {
case active, inactive
mutating func toggle() {
switch self {
case .active: self = .inactive
case .inactive: self = .active
}
}
}
var status = Status.active
status.toggle() // .inactive
Protocols
Protocol Definition
protocol Drawable {
var area: Double { get }
func draw()
}
struct Circle: Drawable {
var radius: Double
var area: Double {
.pi * radius * radius
}
func draw() {
print("Drawing circle")
}
}
Protocol Inheritance
protocol Named {
var name: String { get }
}
protocol Aged: Named {
var age: Int { get }
}
struct Person: Aged {
var name: String
var age: Int
}
Protocol Composition
protocol Named { }
protocol Aged { }
func greet(_ person: Named & Aged) {
// Requires both protocols
}
Protocol Extensions
extension Drawable {
func describe() -> String {
"Area: \(area)"
}
}
// All Drawable types now have describe()
Extensions
Adding Methods
extension Int {
func squared() -> Int {
self * self
}
}
5.squared() // 25
Adding Computed Properties
extension Double {
var km: Double { self * 1000 }
var m: Double { self }
var cm: Double { self / 100 }
}
let distance = 5.km // 5000.0
Adding Initializers
extension Rect {
init(size: Double) {
self.init(width: size, height: size)
}
}
Conditional Conformance
extension Array: Drawable where Element == Int {
var area: Double { Double(count) }
func draw() { print("Drawing array") }
}
// Only [Int] conforms to Drawable
Error Handling
Defining Errors
enum FileError: Error {
case notFound
case permissionDenied
case unknown
}
Throwing Functions
func readFile(name: String) throws -> String {
guard name != "" else {
throw FileError.notFound
}
return "File contents"
}
Try-Catch
do {
let contents = try readFile(name: "data.txt")
print(contents)
} catch FileError.notFound {
print("File not found")
} catch FileError.permissionDenied {
print("Permission denied")
} catch {
print("Unknown error: \(error)")
}
Optional Try
// Returns nil on error
let contents = try? readFile(name: "data.txt")
// Crashes on error (use only if certain)
let contents = try! readFile(name: "data.txt")
// ⚠️ try! crashes if function throws
Defer
func processFile() {
let file = openFile()
defer {
closeFile(file) // Always executes before return
}
if error { return }
// closeFile still called
}
Generics
Generic Functions
func swap<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 1, y = 2
swap(&x, &y) // x=2, y=1
Generic Types
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
items.popLast()
}
}
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
stack.pop() // Optional(2)
Generic Constraints
func findIndex<T: Equatable>(of item: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == item {
return index
}
}
return nil
}
// T must conform to Equatable
Where Clauses
func allEqual<T, U>(_ a: T, _ b: U) -> Bool
where T: Equatable, U: Equatable, T == U {
a == b
}
Common Patterns
Map, Filter, Reduce
let numbers = [1, 2, 3, 4, 5]
// Map: Transform each element
numbers.map { $0 * 2 } // [2, 4, 6, 8, 10]
// Filter: Keep elements matching predicate
numbers.filter { $0 % 2 == 0 } // [2, 4]
// Reduce: Combine elements
numbers.reduce(0, +) // 15
numbers.reduce(1, *) // 120
CompactMap
let strings = ["1", "2", "three", "4"]
let numbers = strings.compactMap { Int($0) }
// [1, 2, 4] - removes nils
FlatMap
let nested = [[1, 2], [3, 4], [5]]
nested.flatMap { $0 } // [1, 2, 3, 4, 5]
Chaining
[1, 2, 3, 4, 5]
.filter { $0 % 2 == 0 }
.map { $0 * 2 }
.reduce(0, +) // 12
Guard Let Pattern
func process(value: Int?) {
guard let value = value else {
return
}
// value available for rest of function
print(value)
}
Pattern Matching
let point = (x: 1, y: 2)
switch point {
case (0, 0):
print("origin")
case (let x, 0):
print("x-axis at \(x)")
case (0, let y):
print("y-axis at \(y)")
case (let x, let y) where x == y:
print("diagonal")
default:
print("other")
}
Gotchas
Value vs Reference Types
struct Point { var x = 0 } // Value type (copy)
class Rect { var x = 0 } // Reference type (shared)
var p1 = Point()
var p2 = p1
p2.x = 5
print(p1.x) // 0 (copy)
var r1 = Rect()
var r2 = r1
r2.x = 5
print(r1.x) // 5 (reference)
String Indices
let str = "Hello"
// ⚠️ Cannot use Int indices
// str[0] // Error!
// Use String.Index
let index = str.index(str.startIndex, offsetBy: 1)
str[index] // "e"
// Safe subscripting
if let index = str.firstIndex(of: "l") {
str[index] // "l"
}
Array COW (Copy-on-Write)
var arr1 = [1, 2, 3]
var arr2 = arr1 // Shared storage
arr2.append(4) // Now copies
// arr1: [1, 2, 3]
// arr2: [1, 2, 3, 4]
Weak References
class Person {
var name: String
weak var friend: Person? // Prevent retain cycle
init(name: String) {
self.name = name
}
}
// ⚠️ Use weak for delegate patterns
Type Inference Limits
// Can be slow to compile
let result = [1, 2, 3]
.map { $0 * 2 }
.filter { $0 > 2 }
.map { "\($0)" }
// Faster: Add explicit types
let doubled: [Int] = [1, 2, 3].map { $0 * 2 }
let filtered: [Int] = doubled.filter { $0 > 2 }
let strings: [String] = filtered.map { "\($0)" }
Mutating Struct Methods
struct Counter {
var count = 0
mutating func increment() {
count += 1
}
}
var counter = Counter()
counter.increment() // OK
let constCounter = Counter()
// constCounter.increment() // Error: let is immutable
// ⚠️ mutating methods require var
Also see
- Swift Programming Language (docs.swift.org)
- Swift Standard Library (developer.apple.com)
- Swift Evolution (github.com)
- Swift by Sundell (swiftbysundell.com)
- Hacking with Swift (hackingwithswift.com)