Compare

Lang Compare

Swift

App Development

Overview

Modern, safe language by Apple. Primary language for iOS, macOS, watchOS, and tvOS development. Also used for server-side development.

Resources

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

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>