Programming languages cheat sheetSwift logo

Swift cheat sheet

Swift syntax on one page, grouped by what you are trying to do: optionals, closures, structs vs classes, protocols and async/await, checked against Swift 6.4.

Last updated

Swift is the programming language from Apple for iPhone, Mac and server apps. It is strict in the useful way: values cannot be nil unless their type says so, number types never convert behind your back, and Swift 6 checks for data races before your code runs. The reference below is grouped by what you are trying to do, and the filter box searches all of it at once. Type optional and every way to unwrap one comes to you, or type 6.0 to see what arrived in that release.

Every snippet is checked against Swift 6.4, the current release, in Swift 6 language mode. Anything that needs a version newer than 5.7 says so in the notes column. Names like user and nums are placeholders for your own. You do not need a Mac to follow along: the official Docker image runs a file in one line, docker run --rm -v "$PWD":/app -w /app swift:6.4 swift main.swift, and the Docker cheat sheet has the rest. Coming from another language? The Ruby and PHP cheat sheets are grouped the same way, so they read side by side.

Running Swift

TaskCommandNotes
Check which version you haveswift --version
Run a scriptswift main.swiftRuns in Swift 5 language mode. Add -swift-version 6 to match a package
Open an interactive shellswift repl
Compile one file to a binaryswiftc main.swift -o hello
Start a command-line appswift package init --type executableLeave out --type for a library
Build a packageswift build
Build and runswift run
Build an optimised releaseswift build -c release
Run the testsswift test
Run the tests matching a nameswift test --filter GreeterTests
Add a dependencyswift package add-dependency https://github.com/apple/swift-argument-parser --from 1.5.0Then add-target-dependency to use it in a target
Update dependencies within their rangesswift package update
Format every file in placeswift format --in-place --recursive Sourcesswift-format ships with the toolchain from 6.0

Xcode includes Swift on a Mac. On Linux and Windows, or to run a version other than the one Xcode ships, swiftly from swift.org installs and switches between toolchains.

Constants and variables

TaskCodeNotes
Print a lineprint("Hello")
Print several valuesprint(a, b, separator: ", ")
Constantlet maxUsers = 100Cannot be reassigned. Use let unless the value changes
Variablevar count = 0
Give the type explicitlylet price: Double = 9.99Usually inferred. 0.5 is a Double, 1 is an Int
Declare now, assign once laterlet label: StringAssign it in every branch before reading it
Several on one linevar x = 0, y = 0
Sized integer typesInt Int64 UInt8Int matches the platform word size, 64-bit almost everywhere
Readable big numbers1_000_000
Whole-number division7 / 2Gives 3. Double(7) / 2 gives 3.5
Remainder7 % 2
Convert between number typesDouble(count) Int(3.9)Int(3.9) is 3, it truncates
String to numberInt("42")Returns Int?, which is nil for junk
Tuple with named partslet point = (x: 3, y: 4)Then point.x
Unpack a tuplelet (width, height) = size
Swap two variablesswap(&a, &b)
Give a type a second nametypealias UserID = Int
What type is ittype(of: value)
Is it a typevalue is String
Cast, or nil if it failsvalue as? Stringas! crashes instead of returning nil
Comment// one line /* several */

Swift never converts between number types for you. count + price is a compile error when one is an Int and the other a Double, so wrap one side: Double(count) + price.

Optionals

An optional is a value that might be missing. String? holds either a String or nil, and Swift will not let you use it as a String until you unwrap it.

TaskCodeNotes
Declare an optionalvar nickname: String? = nil
Use it only if it has a valueif let nickname { print(nickname) }Short for if let nickname = nickname
Unwrap or leave earlyguard let user else { return }user is unwrapped for the rest of the scope
Unwrap several at onceif let first, let last { }
Unwrap and test togetherif let age, age >= 18 { }
Default when nillet name = nickname ?? "Anonymous"
Reach through optionalsuser?.address?.cityThe whole chain is nil if any link is
Call a method only if not nildelegate?.didFinish()
Transform the value if there is onenickname.map { $0.uppercased() }Still optional afterwards
Transform with a step that can failinput.flatMap { Int($0) }Avoids an Int??
Parse a list, keeping what worked["1", "x", "3"].compactMap { Int($0) }Gives [1, 3]
Is it nilnickname == nil
Force unwrapurl!Crashes on nil. Only where nil means a bug
Set up after init, used as if not optionalvar connection: Connection!Crashes if read while still nil
Match on a missing valueif case nil = cache[key] { }

Strings

TaskCodeNotes
Interpolate"Hello, \(name)"Any expression works inside the brackets
Several lines""" ... """The closing quotes set the indentation to strip
Backslashes kept as typed#"C:\path\new"#Interpolate with \#(name)
Join stringsfirst + " " + last
Appends += "!" s.append("!")
Length in characterss.count"café".count is 4
Length in bytess.utf8.count"café".utf8.count is 5
Is it emptys.isEmptyFaster than s.count == 0
Change cases.uppercased() s.lowercased()capitalized needs import Foundation
Trim whitespaces.trimmingCharacters(in: .whitespacesAndNewlines)Needs import Foundation
Does it contains.contains("cat")
Does it start or end withurl.hasPrefix("https") file.hasSuffix(".swift")
Replace every matchs.replacing("cat", with: "dog")replacingOccurrences(of:with:) in Foundation does the same
Split into partscsv.split(separator: ",")Gives [Substring] and drops empty parts
Split, keeping empty partscsv.split(separator: ",", omittingEmptySubsequences: false)
Join an array into a stringnames.joined(separator: ", ")
First or last characters.first s.lastCharacter?, nil on an empty string
First or last n characterss.prefix(5) s.suffix(3)Substrings. Wrap in String() to keep one
Character at a positions[s.index(s.startIndex, offsetBy: 2)]There is no s[2]. Characters vary in size
Loop over charactersfor character in s { }
ReverseString(s.reversed())
RepeatString(repeating: "-", count: 20)
Number to stringString(42) "\(price)"
Format a decimalString(format: "%.2f", price)Needs import Foundation
Does it match a patterns.contains(/\d+/)Regex literals, Swift 5.7+. In Swift 5 mode write #/\d+/#
Capture part of a matchif let match = s.firstMatch(of: /(\d{4})-(\d{2})/) { match.1 }match.1 is the first group
Compare ignoring casea.caseInsensitiveCompare(b) == .orderedSameNeeds import Foundation

Collections: arrays

TaskCodeNotes
Create an arrayvar nums = [1, 2, 3]
Empty array of a typevar names: [String] = []
Filled with one valueArray(repeating: 0, count: 5)
Read by positionnums[0]Crashes if out of range
First or last, safelynums.first nums.lastOptional, nil when empty
Is a position in rangenums.indices.contains(i)
Appendnums.append(4) nums += [5, 6]
Insert at a positionnums.insert(0, at: 0)
Remove at a positionnums.remove(at: 0)Returns the removed item
Remove the last itemnums.popLast()nil when empty. removeLast() crashes instead
Remove every matchnums.removeAll { $0 < 0 }
How many, and is it emptynums.count nums.isEmpty
Does it containnums.contains(3)
Position of a valuenums.firstIndex(of: 3)nil when missing
A slicenums[1...2] nums.prefix(2) nums.dropFirst()ArraySlice. Array(...) to keep it
Sorted copynums.sorted() nums.sorted(by: >)sort() sorts in place
Sort by a fieldusers.sorted { $0.age < $1.age }
Sort by two fieldsusers.sorted { ($0.last, $0.first) < ($1.last, $1.first) }Tuples compare left to right
Transform every itemnums.map { $0 * 2 }
Read one property from eachusers.map(\.name)A key path works as a function
Keep items that pass a testnums.filter { $0.isMultiple(of: 2) }
Fold into one valuenums.reduce(0, +)There is no built-in sum()
Smallest and largestnums.min() nums.max()
Item with the largest fieldusers.max { $0.score < $1.score }
First item that passes a testusers.first { $0.isAdmin }first(where:) with a trailing closure
Do all or any passnums.allSatisfy { $0 > 0 } nums.contains { $0 > 3 }
Count items that pass a testnums.count { $0 > 2 }Swift 6.0+
Loop with the positionfor (i, name) in names.enumerated() { }
Pair up two arrayszip(names, scores)
Flatten nested arraysnested.flatMap { $0 }
Reversed, shuffled, random itemnums.reversed() nums.shuffled() nums.randomElement()
A range as an arrayArray(1...5)1..<5 leaves out the 5

Collections: dictionaries and sets

Dictionaries and sets are unordered. Looping over the same dictionary twice can give a different order, so sort first whenever the order matters.

TaskCodeNotes
Create a dictionaryvar ages = ["Ada": 36, "Alan": 41]
Empty dictionary of a typevar cache: [String: Int] = [:]
Read a valueages["Ada"]Int?, nil when the key is missing
Read with a defaultages["Bob", default: 0]
Set a valueages["Bob"] = 30
Remove a keyages["Bob"] = nilremoveValue(forKey:) returns the old value
Count how often each value appearscounts[word, default: 0] += 1
Does a key existages["Ada"] != nil
Just the keys or valuesages.keys ages.values
Loop over pairsfor (name, age) in ages { }
Loop in key orderfor (name, age) in ages.sorted(by: { $0.key < $1.key }) { }
Change every valueages.mapValues { $0 + 1 }
Keep pairs that pass a testages.filter { $0.value > 30 }
Merge, right side winsdefaults.merge(overrides) { _, new in new }merging(_:uniquingKeysWith:) returns a copy
Group a list by a fieldDictionary(grouping: words, by: \.count)
Index a list by a fieldDictionary(uniqueKeysWithValues: users.map { ($0.id, $0) })Crashes on a duplicate key
Create a setvar tags: Set = ["swift", "ios"]
Add and checktags.insert("server") tags.contains("ios")
Remove duplicates from an arrayArray(Set(nums))Loses the original order
Union, intersection, differencea.union(b) a.intersection(b) a.subtracting(b)
Is one set inside anothera.isSubset(of: b)

Arrays, dictionaries, sets and strings are all value types. Assigning one to a new variable gives you an independent copy, and Swift only duplicates the storage when one of the copies is changed.

Control flow

TaskCodeNotes
If, else if, elseif a { } else if b { } else { }No brackets round the condition. Braces always
Pick one of two valueslet label = count == 1 ? "item" : "items"
If as an expressionlet label = if count == 1 { "item" } else { "items" }Swift 5.9+
Leave early unless a condition holdsguard age >= 18 else { return }The else must leave the scope
Match a value to a resultswitch code { case 200, 201: ok() case 404: missing() default: fail() }Must cover every case. No fallthrough
Match a rangecase 0..<13: "child"
Match with a conditioncase let t where t < 0:
Match a tupleswitch (x, y) { case (0, 0): ... case (0, _): ... }_ matches anything
Switch as an expressionlet text = switch code { case 200: "OK" default: "Error" }Swift 5.9+
Loop over a rangefor i in 0..<5 { }0...5 includes the 5
Loop in stepsfor i in stride(from: 0, to: 100, by: 10) { }through: includes the end
Loop backwardsfor i in (0..<5).reversed() { }
Loop over matching items onlyfor n in nums where n > 0 { }
Repeat n timesfor _ in 1...3 { }
While loopwhile !queue.isEmpty { }
Run the body at least oncerepeat { } while tries < 3
Skip or stopcontinue break
Break out of an outer loopouter: for row in grid { for cell in row { break outer } }
Match one enum caseif case .failure(let error) = result { }
Run code when the scope endsdefer { file.close() }Runs however the scope is left
Is a value in a range(1...10).contains(n)

Functions and closures

TaskCodeNotes
Define a functionfunc add(_ a: Int, _ b: Int) -> Int { a + b }One-expression bodies return implicitly
Argument labelfunc greet(person name: String)Call it as greet(person: "Ada")
No argument labelfunc square(_ x: Int) -> IntThe _ removes it
Default parameterfunc greet(_ name: String = "world")
Any number of argumentsfunc sum(_ numbers: Int...) -> Intnumbers is an [Int] inside
Change the caller's variablefunc double(_ n: inout Int)Call it as double(&count)
Return two valuesfunc bounds(_ values: [Int]) -> (min: Int, max: Int)
Return nothing useful, silently@discardableResult func save() -> BoolNo unused-result warning
Store a functionlet operation: (Int, Int) -> Int = add
Closurelet square = { (x: Int) -> Int in x * x }
Shorthand parametersnums.map { $0 * 2 }
Trailing closurenums.sorted { $0 > $1 }The last closure argument moves outside the brackets
Pass an operator as a functionnums.sorted(by: >)
Closure kept to run laterfunc onDone(_ handler: @escaping () -> Void)Required when the closure outlives the call
Avoid a retain cycle in a class{ [weak self] in self?.reload() }
Closure that keeps statevar total = 0; return { total += 1; return total }It captures total, not a copy
Generic functionfunc firstItem<T>(_ items: [T]) -> T?
Generic with a constraintfunc largest<T: Comparable>(_ items: [T]) -> T?
Function that never returnsfunc fail() -> Never { fatalError() }

Structs and classes

Structs are values: assigning or passing one copies it. Classes are references: two variables can point at one object, so a change through either shows through both. Start with a struct and switch to a class when you need shared identity, inheritance or deinit.

TaskCodeNotes
Define a structstruct Point { var x: Double; var y: Double }Gets a memberwise init for free
Create onevar p = Point(x: 1, y: 2)
Method that changes a structmutating func move(by dx: Double) { x += dx }
Computed propertyvar area: Double { width * height }
Run code when a property changesvar score = 0 { didSet { print("now \(score)") } }willSet runs before
Property built on first uselazy var parser = Parser()
Shared by the type, not each valuestatic let origin = Point(x: 0, y: 0)
Readable outside, writable only insideprivate(set) var count = 0
Init that can failinit?(_ value: Int) { guard value >= 0 else { return nil } }
Define a classclass User { var name: String; init(name: String) { self.name = name } }No memberwise init. You write one
Inheritclass Admin: User
Replace a parent methodoverride func describe() -> String
Call the parent's initsuper.init(name: name)After setting the subclass's own properties
Stop anyone subclassingfinal class Cache
Same objecta === bClasses only. == compares values
Clean up when freeddeinit { }Classes only
Reference that does not keep it aliveweak var delegate: (any TableDelegate)?Always optional, becomes nil when freed
Access levelsprivate fileprivate internal publicinternal is the default: visible in the same module

Enums

TaskCodeNotes
Define an enumenum Direction { case north, south, east, west }
Use a casevar heading = Direction.northThen heading = .south, the type is known
Switch over itswitch heading { case .north: ... default: ... }Cover every case and you need no default
String raw valuesenum Role: String { case admin, member }Role.admin.rawValue is "admin"
Int raw valuesenum Planet: Int { case mercury = 1, venus, earth }venus is 2, earth is 3
Create from a raw valueRole(rawValue: "admin")Optional, nil for an unknown value
Case that carries dataenum Payment { case card(last4: String), cash }
Read the data outcase .card(let last4):
Check one caseif case .card = payment { }
List every caseenum Size: CaseIterableThen Size.allCases
Property on an enumvar label: String { switch self { case .small: "S" case .large: "L" } }
Compare casesheading == .northAutomatic unless a case carries data
Order cases by declarationenum Medal: Comparable { case bronze, silver, gold }
Enum that contains itselfindirect enum Tree { case leaf(Int), node(Tree, Tree) }

Protocols and extensions

TaskCodeNotes
Define a protocolprotocol Shape { var area: Double { get }; func draw() -> String }
Conform to itstruct Square: Shape { var side: Double; var area: Double { side * side } }
Default implementationextension Shape { func describe() -> String { "Area \(area)" } }
Add to an existing typeextension Int { var isEven: Bool { self % 2 == 0 } }
Conform in an extensionextension User: CustomStringConvertible { var description: String { name } }Controls what print shows
Get == and hashing for freestruct Tag: Hashable { let name: String }Works when every property is Hashable
Make a struct sortablestatic func < (lhs: Version, rhs: Version) -> BoolDeclare Comparable. min, max and sorted then work
Encode and decode JSONstruct Repo: Codable
Protocol only classes can adoptprotocol TableDelegate: AnyObject
Protocol with a placeholder typeprotocol Container { associatedtype Item }
List of mixed conforming typeslet shapes: [any Shape] = [Square(side: 1), Circle(radius: 1)]
Accept any one conforming typefunc render(_ shape: some Shape)Faster than any Shape
Return a type without naming itfunc makeShape() -> some Shape
Check the concrete typeif let square = shape as? Square { }
Combine protocolstypealias Model = Codable & Hashable

Error handling

TaskCodeNotes
Define errorsenum LoginError: Error { case badPassword, locked(minutes: Int) }
Function that can throwfunc login(_ password: String) throws -> User
Throwthrow LoginError.badPassword
Call and handledo { let user = try login(pw) } catch { print(error) }error is bound automatically
Catch one casecatch LoginError.locked(let minutes) { }
Catch by typecatch let error as LoginError { }
Nil instead of an errorlet user = try? login(pw)
Crash instead of an errorlet config = try! loadBundledConfig()Only where failure means a bug
Pass the error on to your callerfunc signIn() throws { let user = try login(pw) }
Throw only one error typefunc parse(_ s: String) throws(ParseError) -> IntTyped throws, Swift 6.0+
Error as a valuelet result = Result { try login(pw) }Then result.get() to throw it again
Readable error textextension LoginError: CustomStringConvertibleLocalizedError in Foundation for UI messages
Check an assumptionprecondition(index >= 0, "negative index")Also checked in release builds. assert is debug only
Code that should be unreachablefatalError("unreachable")

Every call that can throw is marked with try, so you can see where control might leave a function. Swift errors are ordinary values, not exceptions with a stack unwind, and they cost about as much as a normal return.

Async and await

TaskCodeNotes
Async functionfunc fetchUser(id: Int) async throws -> User
Call itlet user = try await fetchUser(id: 1)try before await
Run two calls at onceasync let a = fetchUser(id: 1)Then let (x, y) = try await (a, b)
Run a list of calls at oncetry await withThrowingTaskGroup(of: User.self) { group in }group.addTask for each, then for try await
Start async work from sync codeTask { await refresh() }
Get a task's resultlet user = try await task.value
Pausetry await Task.sleep(for: .seconds(1))
Cancel a tasktask.cancel()
Stop if cancelledtry Task.checkCancellation()Or read Task.isCancelled
Protect shared mutable stateactor Counter { var value = 0; func increment() { value += 1 } }One caller at a time inside
Call into an actorawait counter.increment()
Run on the main thread@MainActor func updateUI()Where UI code lives
Run off the caller's actor@concurrent func decode() asyncSwift 6.2+. For heavy work
Safe to share between tasksstruct Message: Sendable
Produce values over timelet (stream, continuation) = AsyncStream.makeStream(of: Int.self)continuation.yield(1), then finish()
Consume values over timefor await value in stream { }
Wrap a callback APItry await withCheckedThrowingContinuation { continuation in }Resume it exactly once
Await in cleanup codedefer { await resource.close() }Swift 6.4+

Swift 6 language mode checks for data races at compile time. New packages also turn on approachable concurrency, which keeps a nonisolated async function on its caller's actor unless it is marked @concurrent, so most code runs where you expect and only the slow parts move off.

A model, start to finish

Most of the reference above in one place: an enum with raw values and a computed property, a struct with two initialisers, Comparable, CustomStringConvertible and a method with typed throws.

enum Currency: String, CaseIterable {
    case gbp, usd
 
    var symbol: String {
        switch self {
        case .gbp: "£"
        case .usd: "$"
        }
    }
}
 
enum MoneyError: Error {
    case currencyMismatch(Currency, Currency)
}
 
struct Money: Comparable, CustomStringConvertible {
    let pence: Int
    let currency: Currency
 
    init(pence: Int, currency: Currency = .gbp) {
        self.pence = pence
        self.currency = currency
    }
 
    init(pounds: Double, currency: Currency = .gbp) {
        self.init(pence: Int((pounds * 100).rounded()), currency: currency)
    }
 
    var description: String {
        let sign = pence < 0 ? "-" : ""
        let whole = abs(pence) / 100
        let fraction = abs(pence) % 100
        let padded = fraction < 10 ? "0\(fraction)" : "\(fraction)"
        return "\(sign)\(currency.symbol)\(whole).\(padded)"
    }
 
    func adding(_ other: Money) throws(MoneyError) -> Money {
        guard other.currency == currency else {
            throw .currencyMismatch(currency, other.currency)
        }
        return Money(pence: pence + other.pence, currency: currency)
    }
 
    static func < (lhs: Money, rhs: Money) -> Bool {
        lhs.pence < rhs.pence
    }
}
 
let price = Money(pence: 1999)
let total = try price.adding(Money(pounds: 5))
print(total)                              // £24.99
print([price, Money(pence: 500)].max()!)  // £19.99
 
do {
    _ = try price.adding(Money(pence: 100, currency: .usd))
} catch .currencyMismatch(let a, let b) {
    print("Cannot add \(a) to \(b)")      // Cannot add gbp to usd
}

Defining < is enough for Comparable to give you >, <=, >=, min, max and sorted, and == comes for free because every stored property is already Equatable. Money is held in pence as an Int on purpose: a Double cannot hold most decimal fractions exactly, so 0.1 + 0.2 == 0.3 is false in Swift, as it is in almost every language. Because adding throws only MoneyError, the catch can match .currencyMismatch directly and needs no catch-all.

Structs copy, classes share

The difference between value and reference types is the one to get into your fingers early, because the code looks identical until it behaves differently.

struct Settings {
    var theme = "light"
}
 
final class Session {
    var userName: String
    init(userName: String) { self.userName = userName }
}
 
var original = Settings()
var copy = original
copy.theme = "dark"
print(original.theme)          // light: the struct was copied
 
let session = Session(userName: "Ada")
let sameSession = session
sameSession.userName = "Grace"
print(session.userName)        // Grace: both names point at one object
print(session === sameSession) // true

Notice that sameSession is a let and its property still changed. For a class, let fixes which object the name refers to, not what is inside the object. For a struct, let freezes the whole value.

Decoding JSON with Codable

Declare Codable and the compiler writes the encoding and decoding. A CodingKeys enum maps Swift names to the keys in the JSON, and an optional property accepts null or a missing key.

import Foundation
 
struct Repo: Codable {
    let id: Int
    let fullName: String
    let stars: Int
    let description: String?
 
    enum CodingKeys: String, CodingKey {
        case id
        case fullName = "full_name"
        case stars = "stargazers_count"
        case description
    }
}
 
let json = """
{ "id": 1, "full_name": "swiftlang/swift", "stargazers_count": 69000, "description": null }
"""
 
do {
    let repo = try JSONDecoder().decode(Repo.self, from: Data(json.utf8))
    print(repo.fullName, repo.stars)  // swiftlang/swift 69000
 
    let encoder = JSONEncoder()
    encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
    let data = try encoder.encode(repo)
    print(String(decoding: data, as: UTF8.self))
} catch {
    print("Could not decode: \(error)")
}

When every key is snake_case, decoder.keyDecodingStrategy = .convertFromSnakeCase does the mapping for you and the CodingKeys enum can go. If decoding fails, print the error rather than error.localizedDescription: the full DecodingError names the key and the path to it.

Concurrent work with a task group and an actor

A task group runs one child task per item and collects the results as they finish. The actor keeps the cache safe while several of those tasks read and write it.

struct Profile: Sendable {
    let id: Int
    let name: String
}
 
enum FetchError: Error {
    case notFound(id: Int)
}
 
actor ProfileCache {
    private var profiles: [Int: Profile] = [:]
 
    func profile(for id: Int) -> Profile? { profiles[id] }
    func store(_ profile: Profile) { profiles[profile.id] = profile }
}
 
func fetchProfile(id: Int) async throws -> Profile {
    try await Task.sleep(for: .milliseconds(100)) // stands in for a network call
    guard id > 0 else { throw FetchError.notFound(id: id) }
    return Profile(id: id, name: "User \(id)")
}
 
func loadProfiles(ids: [Int], cache: ProfileCache) async throws -> [Profile] {
    try await withThrowingTaskGroup(of: Profile.self) { group in
        for id in ids {
            group.addTask {
                if let cached = await cache.profile(for: id) { return cached }
                let profile = try await fetchProfile(id: id)
                await cache.store(profile)
                return profile
            }
        }
 
        var profiles: [Profile] = []
        for try await profile in group {
            profiles.append(profile)
        }
        return profiles.sorted { $0.id < $1.id }
    }
}
 
let cache = ProfileCache()
let profiles = try await loadProfiles(ids: [1, 2, 3], cache: cache)
print(profiles.map(\.name)) // ["User 1", "User 2", "User 3"], in about 0.1s, not 0.3s
 
do {
    _ = try await loadProfiles(ids: [1, -1], cache: cache)
} catch FetchError.notFound(let id) {
    print("No profile \(id)")   // No profile -1
}

Results arrive in the order the tasks finish, not the order they started, which is why the list is sorted before it is returned. When one child task throws, the group cancels the rest and the error comes out of loadProfiles. Every call into the actor is an await, because the caller may have to wait its turn.

A package, start to finish

swift package init --type executable writes a Package.swift like this one, and swift package add-dependency fills in the dependencies. A library target holds the logic, so the tests can import it, and a small executable target wraps it.

// swift-tools-version: 6.2
import PackageDescription
 
let package = Package(
    name: "Greeter",
    platforms: [.macOS(.v15)],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
    ],
    targets: [
        .target(name: "Greeter"),
        .executableTarget(
            name: "greet",
            dependencies: [
                "Greeter",
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ]
        ),
        .testTarget(name: "GreeterTests", dependencies: ["Greeter"]),
    ]
)

Tests use Swift Testing, which ships with the toolchain. swift test finds every @Test function, and #expect shows both sides of a failed comparison.

// Tests/GreeterTests/GreeterTests.swift
import Testing
@testable import Greeter
 
@Test func greetsByName() {
    #expect(greeting(for: "Ada") == "Hello, Ada!")
}
 
@Test(arguments: ["", "Grace"])
func neverEmpty(name: String) {
    #expect(!greeting(for: name).isEmpty)
}

The first line of Package.swift is not a comment you can delete: it sets the oldest toolchain that can build the package and, from 6.0, turns on Swift 6 language mode. Commit Package.resolved for an app so every build gets the same dependency versions, and the Git cheat sheet covers the rest of that workflow.

Gotchas

The mistakes almost everyone makes in their first month of Swift.

Looks rightWhat actually happensDo this instead
let total = count + price with an Int and a DoubleCompile error: no implicit number conversionDouble(count) + price
name[0] on a StringCompile error: strings are not indexed by Intname.first, name.prefix(1), or Array(name)[0]
nums[5] on a shorter arrayCrashes at runtimenums.indices.contains(5) first, or nums.first
let user = users.first then user.nameCompile error: user is optionalif let user, guard let user, or user?.name
print(nickname) on a String?Prints Optional("kit")Unwrap first, or nickname ?? ""
for (k, v) in dict expecting insertion orderOrder is arbitrary and can change between runsdict.sorted(by: { $0.key < $1.key })
var copy = session on a classBoth names share one objectUse a struct, or write your own copy method
s.contains(/\d+/) in swift file.swiftSyntax errors, because scripts default to Swift 5 modeswift -swift-version 6 file.swift, or #/\d+/#
self.onUpdate = { self.reload() } in a classA retain cycle: neither object is ever freed{ [weak self] in self?.reload() }
Int("3.5")nil, not 3Double("3.5").map { Int($0) }

Swift's sort and sorted are guaranteed to be stable: items that compare equal keep their original order, so sorting by surname after sorting by first name gives the order you expect. Under the hood it is an adaptive merge sort, the same family as merge sort, unlike quick sort, which makes no such promise. Both are on the site as step-through visualisations.

Common questions

Which version of Swift does this cheat sheet cover?

Swift 6.4, the current release, which came out in September 2026, in Swift 6 language mode. Most of the page also works on Swift 5.9 and later, and anything newer than 5.7 says so in the notes column, for example count(where:) and typed throws need 6.0, @concurrent needs 6.2 and await inside defer needs 6.4. Run swift --version to see which version you have.

What is the difference between let and var in Swift?

let declares a constant, which cannot be reassigned after its first value. var declares a variable, which can. For a struct, let also freezes every property inside it, while for a class let only fixes which object the constant points at, and the object's var properties can still change. The compiler warns when a var is never changed, so start with let and switch when you need to.

Should I use a struct or a class?

Use a struct by default. Structs are copied when assigned or passed, so no other part of the program can change your copy, and they are cheaper to create. Reach for a class when you need one shared object that several parts of the program see change, when you need inheritance, or when you need deinit to run cleanup when the object goes away.

What does the question mark and exclamation mark mean in Swift?

A question mark after a type, as in String?, makes it optional: it may hold a value or nil. After a value, as in user?.name, it is optional chaining, which gives nil instead of crashing when user is nil. An exclamation mark force unwraps an optional, as in url!, and crashes the program if the value is nil, so keep it for values that can only be nil because of a bug.

What is the difference between if let and guard let?

Both unwrap an optional. if let makes the unwrapped value available only inside its braces, which suits doing something extra when a value is there. guard let makes it available for the rest of the function and requires its else branch to leave, with return, throw, break or continue, which suits checking requirements at the top of a function without nesting the rest of it.

What is the difference between any and some?

some Shape means one specific type that conforms to Shape, chosen by the caller or the function, and fixed for that call. The compiler knows the real type, so it is fast. any Shape is a box that can hold any conforming type, and different boxes can hold different types, which is what you need for a mixed array such as [any Shape]. Prefer some unless you need the mixing.

Do I need a Mac to learn Swift?

No. Swift is open source and runs on Linux and Windows as well as macOS. Every snippet on this page was checked with the official swift Docker image on Linux. You need a Mac and Xcode to build apps for iPhone, iPad and Mac, and some Apple frameworks such as SwiftUI and UIKit only exist there, but the language itself, Foundation and Swift packages work everywhere.

Why can't I index a Swift string with a number?

Because Swift counts characters as people see them, and one character can be several bytes, as with accented letters and emoji. Finding the tenth character means walking the string from the start, so Swift makes that cost visible with String.Index instead of pretending it is instant. Use prefix, suffix, first and last where you can, and convert with Array(s) if you really need random access.

See all cheat sheets

Want this explained by a cat?

The videos cover the same ground in sixty seconds. If there is a tool you want a cheat sheet for next, ask.