Swift
App DevelopmentOverview
Modern, safe language by Apple. Primary language for iOS, macOS, watchOS, and tvOS development. Also used for server-side development.
Resources
Popular learning and reference links:
Installation & Getting Started
Swift comes bundled with Xcode on macOS. On Linux, install via the official toolchain or swiftly.
# macOS — included with Xcode
xcode-select --install
swift --version
# macOS — standalone (without Xcode)
brew install swift
# Linux (Ubuntu)
# Download from https://swift.org/download/
# Or use swiftly (version manager)
curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash
swiftly install latest
# Windows — download from https://swift.org/download/
# Verify
swift --version
# REPL — Swift has a built-in REPL
swift # Start REPL
# > let x = 42
# > print(x)
# Swift Playgrounds — interactive learning (macOS/iPad)
# Xcode Playgrounds — rapid prototyping in Xcode
# Run a script
swift hello.swift
# Run a package
swift run
# Online playground
# https://swiftfiddle.com/ Project Scaffolding
Use SPM for command-line tools or Xcode for app projects.
# Create a new package (library)
swift package init --name MyLib
# Create an executable
swift package init --type executable --name MyApp
# Project structure created:
# MyApp/
# Package.swift
# Sources/
# main.swift
# Build and run
swift build
swift run
# Create an iOS/macOS app
# Open Xcode > File > New > Project
# Select template (App, Framework, etc.)
# Build for release
swift build -c release Package Management
Swift Package Manager (SPM) is built into Swift and Xcode.
# Add a dependency in Package.swift:
# dependencies: [
# .package(url: "https://github.com/Alamofire/Alamofire",
# from: "5.0.0")
# ]
# Resolve dependencies
swift package resolve
# Update dependencies
swift package update
# Show dependencies
swift package show-dependencies
# In Xcode: File > Add Package Dependencies
# Paste the repository URL and select version Tooling & Formatter/Linter
Swift uses swift-format (Apple’s official formatter) or SwiftLint for linting. Xcode provides built-in formatting.
# swift-format — official formatter
brew install swift-format
swift-format format --in-place Sources/
swift-format lint Sources/
# SwiftLint — community linter (widely adopted)
brew install swiftlint
swiftlint
swiftlint --fix # Auto-fix
# .swiftlint.yml
disabled_rules:
- trailing_whitespace
opt_in_rules:
- empty_count
- closure_spacing
- force_unwrapping
excluded:
- .build
- Packages
line_length:
warning: 120
error: 150
// .swift-format (JSON config)
{
"version": 1,
"indentation": { "spaces": 4 },
"lineLength": 120,
"maximumBlankLines": 1,
"respectsExistingLineBreaks": true
}
# Xcode: Editor > Structure > Re-Indent (Ctrl+I)
# Periphery — detect unused code
brew install peripheryapp/periphery/periphery
periphery scan Build & Compile Model
Ahead-of-time compiled via LLVM. Swift compiles to native code. Apps can also use the Swift runtime for dynamic features.
# Compile a single file
swiftc hello.swift -o hello
# Build with SPM
swift build # Debug
swift build -c release # Release
# Run
swift run
# Xcode build
xcodebuild -scheme MyApp -configuration Release build
# Output: native binary (macOS/Linux), .app bundle (macOS/iOS)
Execution model:
- Source → SIL (Swift Intermediate Language) → LLVM IR → Machine code
- Whole-module optimization (
-whole-module-optimization) for release builds - Swift runtime is included in the OS on Apple platforms (since iOS 12.2)
- On Linux, the Swift runtime is bundled with the binary
- Supports both static and dynamic linking
- Incremental compilation for fast debug builds
Libraries & Frameworks
Swift’s ecosystem is centered around Apple platforms but expanding to server-side and cross-platform.
Apple Frameworks - SwiftUI, UIKit, AppKit, Combine, Core Data, CloudKit, ARKit, Metal
Server-Side - Vapor, Hummingbird, Smoke (AWS)
Networking - Alamofire, URLSession (stdlib), AsyncHTTPClient, Moya
Databases - Fluent (Vapor ORM), GRDB, SQLite.swift, Core Data, SwiftData
Reactive - Combine (stdlib), RxSwift, OpenCombine
UI Components - SnapKit (Auto Layout), Kingfisher (images), Lottie (animations)
Architecture - The Composable Architecture (TCA), swift-dependencies
Testing - Swift Testing, XCTest, Quick/Nimble, ViewInspector
CLI - Swift Argument Parser, Rainbow, swift-log
Concurrency - Swift Concurrency (async/await, actors), swift-async-algorithms
Cross-Platform - Skip (Swift to Kotlin/Android), SwiftCrossUI
Testing
Swift has a built-in testing framework: Swift Testing (new, from Swift 5.9+) and XCTest (traditional). Both integrate with Xcode and SPM.
// Swift Testing (modern, recommended)
import Testing
@Test func addition() {
#expect(1 + 2 == 3)
}
@Test("Addition with negatives")
func negativeAddition() {
#expect(-1 + 1 == 0)
}
// Parameterized tests
@Test(arguments: [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
])
func add(a: Int, b: Int, expected: Int) {
#expect(a + b == expected)
}
// XCTest (traditional)
import XCTest
class MathTests: XCTestCase {
func testAdd() {
XCTAssertEqual(1 + 2, 3)
}
func testPerformance() {
measure {
// Code to benchmark
}
}
}
# Run tests
swift test # SPM projects
swift test --filter MathTests # Specific test
# In Xcode: Cmd+U to run all tests
# In Xcode: Click diamond next to test to run individually Debugging
Xcode debugger (LLDB-based) is the primary tool. Swift also supports command-line LLDB and VS Code debugging.
// Print debugging
print("value:", value)
print("struct:", myStruct) // Uses CustomStringConvertible
debugPrint(myStruct) // Uses CustomDebugStringConvertible
dump(myStruct) // Deep recursive print
// Assertions and preconditions
assert(index >= 0, "Index must be non-negative")
precondition(array.count > 0, "Array must not be empty")
// assert: removed in release builds
// precondition: kept in release builds
// fatalError — always crashes (useful for unimplemented)
fatalError("Not implemented")
// Logging (os.Logger — Apple Unified Logging)
import os
let logger = Logger(subsystem: "com.app", category: "network")
logger.info("Request started")
logger.error("Failed: \(error.localizedDescription)")
logger.debug("Response: \(data, privacy: .private)")
# Xcode debugging
# Cmd+R: Run, Cmd+Y: Toggle breakpoints
# Click gutter for breakpoints
# Conditional breakpoints, symbolic breakpoints
# Debug navigator: CPU, memory, disk, network
# View debugger: 3D UI hierarchy
# Memory graph debugger: find retain cycles
# LLDB commands in Xcode console
(lldb) po myVariable # Print object
(lldb) p myInt # Print value
(lldb) expr myVar = 42 # Modify at runtime
(lldb) bt # Backtrace
# Command-line debugging
lldb .build/debug/MyApp
# b main.swift:42, r, n, s, po variable
# VS Code — Swift extension (on Linux/macOS) Variables
Use let for constants and var for mutable variables. Type inference is the default.
// Constants (immutable)
let name = "Alice"
let age = 30
// Variables (mutable)
var count = 0
count += 1
// Explicit type annotation
let pi: Double = 3.14159
var label: String = "hello"
// Optionals
var maybeValue: String? = nil
maybeValue = "exists"
// Tuple destructuring
let (x, y) = (1, 2)
let point = (x: 10, y: 20)
print(point.x) Types
Statically typed with structs, classes, enums, protocols, and generics.
// Primitives
let s: String = "hello"
let n: Int = 42
let f: Double = 3.14
let b: Bool = true
// Arrays and Dictionaries
let nums: [Int] = [1, 2, 3]
let ages: [String: Int] = ["Alice": 30, "Bob": 25]
let unique: Set<Int> = [1, 2, 3]
// Structs (value types)
struct Point {
var x: Double
var y: Double
}
// Enums (with associated values)
enum Shape {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
}
// Protocols (like interfaces)
protocol Drawable {
func draw()
}
// Optionals
let maybe: Int? = 42
let empty: Int? = nil Data Structures
Built-in value types: Array, Dictionary, Set. All generic and copy-on-write. Swift Collections package adds more.
// Array — ordered, generic
var arr = [1, 2, 3]
arr.append(4)
arr.remove(at: 0)
arr.contains(2) // true
arr.count // 3
arr[0...1] // ArraySlice [2, 3]
arr.first // Optional(2)
// Dictionary — key-value
var dict: [String: Int] = ["alice": 95, "bob": 87]
dict["charlie"] = 72
dict["alice"] // Optional(95)
dict.keys // Dictionary.Keys
dict.removeValue(forKey: "bob")
// Set — unique values
var set: Set<Int> = [1, 2, 3]
set.insert(4)
set.contains(2) // true
let intersection = set.intersection([2, 3, 5])
let union = set.union([5, 6])
// Tuples
let point = (x: 10, y: 20)
let (x, y) = point
// Iteration
for (index, value) in arr.enumerated() { }
for (key, value) in dict { }
// Functional operations
let doubled = arr.map { $0 * 2 }
let evens = arr.filter { $0 % 2 == 0 }
let sum = arr.reduce(0, +)
let sorted = arr.sorted(by: >)
// Swift Collections package (extras)
// import Collections
// Deque, OrderedDictionary, OrderedSet, Heap
import Collections
var deque = Deque([1, 2, 3])
deque.prepend(0) Functions
Functions use named parameters by default. Closures are first-class.
// Basic function
func greet(name: String) -> String {
return "Hello, \(name)!"
}
// Argument labels
func move(from start: Int, to end: Int) -> Int {
return end - start
}
move(from: 0, to: 10)
// Default parameters
func power(base: Int, exp: Int = 2) -> Int {
return Int(pow(Double(base), Double(exp)))
}
// Closures
let add = { (a: Int, b: Int) -> Int in a + b }
let doubled = [1, 2, 3].map { $0 * 2 }
// Generic function
func first<T>(_ items: [T]) -> T? {
return items.first
}
// Throwing function
func parse(_ input: String) throws -> Int {
guard let n = Int(input) else {
throw ParseError.invalidInput
}
return n
} Conditionals
if/else, switch with pattern matching, and guard for early exits.
// If / else
if x > 0 {
print("positive")
} else if x == 0 {
print("zero")
} else {
print("negative")
}
// Ternary
let label = x > 0 ? "positive" : "non-positive"
// Switch (exhaustive, no fallthrough)
switch shape {
case .circle(let radius):
print("Circle: \(radius)")
case .rectangle(let w, let h):
print("Rect: \(w)x\(h)")
}
// Guard (early exit)
func process(value: String?) {
guard let unwrapped = value else {
print("no value")
return
}
print(unwrapped)
}
// If-let (optional binding)
if let city = user?.address?.city {
print(city)
}
// Nil coalescing
let val = input ?? "default" Loops
for-in loops, while, and functional methods on collections.
// For-in with range
for i in 0..<5 {
print(i)
}
// For-in over array
for item in ["a", "b", "c"] {
print(item)
}
// Enumerated (index + value)
for (i, val) in ["a", "b", "c"].enumerated() {
print(i, val)
}
// While
var n = 0
while n < 3 {
n += 1
}
// Repeat-while (do-while)
repeat {
n -= 1
} while n > 0
// Functional methods
let squares = (0..<10).map { $0 * $0 }
let evens = [1, 2, 3, 4].filter { $0 % 2 == 0 }
let sum = [1, 2, 3].reduce(0, +) Generics & Type System
Protocol-oriented generics with associated types, where clauses, and opaque types (some). Strong type inference.
// Generic function
func identity<T>(_ value: T) -> T { value }
// Protocol constraints
func largest<T: Comparable>(_ items: [T]) -> T? {
items.max()
}
// Multiple constraints
func process<T: Hashable & CustomStringConvertible>(_ item: T) {
print(item.description)
}
// Generic types
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { items.popLast() }
var isEmpty: Bool { items.isEmpty }
}
var intStack = Stack<Int>()
intStack.push(42)
// Associated types (in protocols)
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
// Where clauses
func allMatch<C: Container>(_ container: C, _ predicate: (C.Item) -> Bool) -> Bool
where C.Item: Equatable
{
container.allSatisfy(predicate)
}
// Opaque types — hide concrete type
func makeCollection() -> some Collection { [1, 2, 3] }
// Existential types (any)
func printAll(_ items: [any CustomStringConvertible]) {
for item in items { print(item.description) }
}
// Primary associated types (Swift 5.7+)
func process(_ items: some Collection<Int>) { } Inheritance & Composition
Single class inheritance with protocols for composition. Protocol extensions provide default implementations. Structs (value types) don’t support inheritance.
// Class inheritance
class Animal {
let name: String
init(name: String) { self.name = name }
func speak() -> String { "\(name) makes a sound" }
}
class Dog: Animal {
override func speak() -> String { "\(name) barks" }
}
// Protocols (composition)
protocol Drawable {
func draw()
}
protocol Printable {
func printOut()
}
class Canvas: Drawable, Printable {
func draw() { /* ... */ }
func printOut() { /* ... */ }
}
// Protocol extensions (default implementations)
protocol Greetable {
var name: String { get }
}
extension Greetable {
func greet() -> String { "Hello, \(name)!" }
}
// Protocol-oriented programming
protocol Shape {
func area() -> Double
}
extension Shape {
func describe() -> String { "Area: \(area())" }
}
// Final (prevent subclassing)
final class Singleton { /* cannot be subclassed */ }
// Structs use protocols, not inheritance
struct Point: Equatable { var x: Double; var y: Double } Functional Patterns
First-class closures, trailing closure syntax, and rich collection methods. Enums with associated values for algebraic data types.
// Closures
let double = { (x: Int) -> Int in x * 2 }
let add: (Int, Int) -> Int = { $0 + $1 }
// Collection methods
let nums = [1, 2, 3, 4, 5]
let squared = nums.map { $0 * $0 }
let evens = nums.filter { $0 % 2 == 0 }
let sum = nums.reduce(0, +)
// Chaining
let result = (0..<100)
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.prefix(10)
.reduce(0, +)
// Optional chaining (like Maybe monad)
let name: String? = "Alice"
let upper = name.map { $0.uppercased() }
let len = name.flatMap { $0.isEmpty ? nil : $0.count }
// Enums as algebraic data types
enum Result<T, E: Error> {
case success(T)
case failure(E)
func map<U>(_ fn: (T) -> U) -> Result<U, E> {
switch self {
case .success(let v): return .success(fn(v))
case .failure(let e): return .failure(e)
}
}
}
// Higher-order functions
func apply<T>(_ fn: (T) -> T, to value: T) -> T {
fn(value)
} Concurrency
Structured concurrency with async/await, actors, and task groups. Built into the language since Swift 5.5.
// async/await
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(
from: URL(string: "https://api.example.com")!
)
return data
}
// Concurrent tasks with async let
async let users = fetchUsers()
async let posts = fetchPosts()
let (u, p) = try await (users, posts)
// Task groups — dynamic concurrency
let results = try await withThrowingTaskGroup(of: String.self) { group in
for url in urls {
group.addTask { try await fetch(url) }
}
var collected: [String] = []
for try await result in group {
collected.append(result)
}
return collected
}
// Actors — safe shared mutable state
actor Counter {
private var count = 0
func increment() { count += 1 }
func value() -> Int { count }
}
let counter = Counter()
await counter.increment()
print(await counter.value())
// MainActor — run on main thread (UI)
@MainActor
func updateUI() {
label.text = "Updated"
}
// Task — launch unstructured concurrent work
Task {
let data = try await fetchData()
await updateUI(with: data)
} Modules & Imports
Swift uses modules (frameworks/packages) as the unit of code distribution. All files in a target share the same namespace. Access control governs visibility.
// Import a module
import Foundation
import UIKit
import SwiftUI
// Import specific symbol
import struct Foundation.URL
import func Darwin.sqrt
// All files in a module share scope — no need to
// import sibling files within the same target
// Access control
public class PublicAPI {} // visible everywhere
internal class DefaultAccess {} // same module (default)
fileprivate class FileOnly {} // same file only
private class ScopeOnly {} // enclosing scope
// Package (SPM) structure:
// Sources/
// MyLib/
// Models/User.swift
// Services/Auth.swift
// MyApp/
// main.swift
// Expose module API in Package.swift:
// .target(name: "MyLib"),
// .target(name: "MyApp", dependencies: ["MyLib"])
// @_exported re-exports a module
@_exported import Foundation Error Handling
Uses do/try/catch with typed errors conforming to the Error protocol.
// Define errors
enum AppError: Error {
case notFound(String)
case invalidInput
}
// Throwing function
func loadFile(name: String) throws -> String {
guard !name.isEmpty else {
throw AppError.invalidInput
}
return "contents"
}
// Do / try / catch
do {
let data = try loadFile(name: "test.txt")
print(data)
} catch AppError.notFound(let name) {
print("Not found: \(name)")
} catch {
print("Error: \(error)")
}
// try? (returns optional)
let maybe = try? loadFile(name: "test.txt")
// try! (force unwrap, crashes on error)
let forced = try! loadFile(name: "safe.txt")
// Defer for cleanup
func process() {
let file = openFile()
defer { closeFile(file) }
// work with file...
} Memory Management
Automatic Reference Counting (ARC) — deterministic, compile-time inserted retain/release. No GC pauses.
// ARC automatically manages reference counts
class Person {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) is being deinitialized") }
}
var person: Person? = Person(name: "Alice") // refcount = 1
var another = person // refcount = 2
person = nil // refcount = 1
another = nil // refcount = 0 → deinit called
// Strong reference cycles — use weak/unowned to break
class Parent {
var child: Child?
}
class Child {
weak var parent: Parent? // Weak — can be nil
unowned let owner: Parent // Unowned — never nil (crash if wrong)
}
// Closures and capture lists
class ViewController {
var name = "Main"
func setup() {
// Strong capture (default) — can cause retain cycle
// fetchData { self.name }
// Weak capture — breaks cycle
fetchData { [weak self] in
guard let self else { return }
print(self.name)
}
// Unowned capture — when you know self outlives closure
fetchData { [unowned self] in
print(self.name)
}
}
}
// Value types (struct, enum) — stack allocated, no ARC
struct Point { var x: Double; var y: Double }
// Copied on assignment, no reference counting needed
// Autoreleasepool (for Objective-C interop)
autoreleasepool {
// Heavy temporary object creation
} Performance Profiling
Instruments (Xcode) is the primary profiling tool. Swift also supports XCTest performance tests and swift-benchmark.
// XCTest performance measurement
import XCTest
class PerformanceTests: XCTestCase {
func testSortPerformance() {
let data = (0..<10000).shuffled()
measure {
_ = data.sorted()
}
}
// Clock-based measurement (Xcode 14+)
func testWithClock() {
measure(metrics: [XCTClockMetric()]) {
doExpensiveWork()
}
}
}
# Instruments (Xcode)
# Product > Profile (Cmd+I) in Xcode
# Templates:
# - Time Profiler: CPU flame chart
# - Allocations: heap memory
# - Leaks: memory leak detection
# - Network: HTTP request profiling
# - Core Animation: UI rendering
# Command-line Instruments
xcrun xctrace record --template 'Time Profiler' \
--launch ./MyApp
# swift-benchmark (for library/CLI benchmarking)
# Package: google/swift-benchmark
import Benchmark
benchmark("sort") {
var array = (0..<1000).shuffled()
array.sort()
}
Benchmark.main()
# os_signpost — custom performance markers
import os
let log = OSLog(subsystem: "com.app", category: "perf")
os_signpost(.begin, log: log, name: "fetch")
// ... work ...
os_signpost(.end, log: log, name: "fetch")
# Visible in Instruments > os_signpost Interop
Seamless Objective-C interop and C interop built in. Swift can import C headers directly. Bridging to other languages via C ABI.
// C interop — import C functions directly
import Darwin // or Glibc on Linux
let result = sqrt(16.0) // C math function
let str = strdup("hello")
free(str)
// Import a C library via module map
// module MyCLib [system] {
// header "mylib.h"
// link "mylib"
// }
import MyCLib
myCFunction()
// Unsafe pointers (for C interop)
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
ptr.pointee = 42
ptr.deallocate()
// Objective-C interop (automatic bridging)
import Foundation
let str = NSString(string: "Hello")
let url = NSURL(string: "https://example.com")
// @objc — expose Swift to Objective-C
@objc class MyClass: NSObject {
@objc func doSomething() { }
}
// Swift ↔ Kotlin (via Skip framework)
// Compiles Swift to Kotlin for Android
// Swift ↔ C++ (Swift 5.9+)
// Enable in build settings: C++ Interoperability = Enabled
// Import C++ headers directly in Swift
// Process — call system commands
import Foundation
let process = Process()
process.executableURL = URL(fileURLWithPath: "/bin/ls")
process.arguments = ["-la"]
try process.run() Packaging & Distribution
Libraries via Swift Package Manager. Apps via App Store (Xcode), Homebrew, or direct binary distribution.
// Package.swift — library distribution
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyLib",
platforms: [.macOS(.v13), .iOS(.v16)],
products: [
.library(name: "MyLib", targets: ["MyLib"]),
.executable(name: "mycli", targets: ["CLI"]),
],
targets: [
.target(name: "MyLib"),
.executableTarget(name: "CLI", dependencies: ["MyLib"]),
.testTarget(name: "MyLibTests", dependencies: ["MyLib"]),
]
)
# Publish — push to any Git repo with a tag
git tag 1.0.0
git push origin 1.0.0
# Users: .package(url: "https://github.com/user/MyLib", from: "1.0.0")
# Swift Package Index — discovery
# https://swiftpackageindex.com/
# Build release binary
swift build -c release
# Output: .build/release/mycli
# App Store distribution (Xcode)
# Product > Archive > Distribute App
# Homebrew formula
# Create a formula pointing to the release binary/tarball
brew tap user/tap
brew install mycli
# Notarization (macOS)
xcrun notarytool submit MyApp.zip --apple-id <email> --team-id <team>