programming4 min read

Swift Tutorial: Learn Apple Language from Scratch (2026)

Swift Tutorial: Learn Apple Language from Scratch (2026)

Published:  |  Category: Programming  |  Reading time: ~15 min
Swift Tutorial: Learn Apple Language from Scratch (2026)

Swift is Apple's bet on a language that is both approachable for beginners and powerful for systems programming. I have used it for iOS apps, server-side APIs with Vapor, and even command-line tools. The language prioritizes safety: optionals force you to handle missing values, and ARC eliminates manual memory management for most cases.

What I appreciate most is how Swift's protocol-oriented design encourages composition over heavy inheritance hierarchies.

Optionals

Optionals represent values that may be absent. Int? is either an Int or nil. Use optional binding (if let) to safely unwrap. Force unwrapping with ! crashes on nil — use it only when you are certain the value exists. Optional chaining (?) propagates nil through property accesses.

var name: String? = "Alice"
if let n = name {
    print("Hello, \(n)")
}

let len = name?.count ?? 0
print(len)

// Force unwrap (risky)
let forced = name!

Closures

Closures are self-contained function blocks that capture references to surrounding variables. They are Swift's lambdas. Trailing closure syntax omits the parameter label when the closure is the last argument. Use $0, $1 for shorthand argument names. Capture lists ([weak self]) prevent retain cycles.

let numbers = [3, 1, 4, 1, 5]
let sorted = numbers.sorted { $0 < $1 }

let doubled = numbers.map { $0 * 2 }

class Foo {
    func setup() {
        NotificationCenter.default.addObserver(
            forName: .someNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.handle()
        }
    }
}

Protocol-Oriented

Swift protocols define interfaces that types can adopt. Protocol extensions provide default implementations. This shifts the paradigm from class hierarchies to protocol composition. Value types (structs and enums) can adopt protocols, making code more flexible and reducing shared mutable state.

protocol Drawable {
    func draw()
}

extension Drawable {
    func draw() { print("default draw") }
}

struct Circle: Drawable {
    var radius: Double
}

struct Square: Drawable {
    var side: Double
    func draw() { print("square: \(side)") }
}

let items: [Drawable] = [Circle(r: 5), Square(s: 3)]

ARC

Automatic Reference Counting tracks strong references to class instances. When the last strong reference is removed, the instance is deallocated. Strong reference cycles occur when two instances hold strong references to each other. Use weak (becomes nil) or unowned (assumes never nil) to break cycles.

class Child {
    weak var parent: Parent?
    deinit { print("child gone") }
}

class Parent {
    var child: Child?
    deinit { print("parent gone") }
}

var p: Parent? = Parent()
p?.child = Child()
p?.child?.parent = p
p = nil // both deallocated, no cycle

Generics

Generics enable type-safe, reusable functions and types. Type constraints with where clauses restrict the allowed types. Associated types in protocols let conforming types specify the concrete type. The compiler generates specialized code for each concrete type.

func swap(_ a: inout T, _ b: inout T) {
    let tmp = a
    a = b
    b = tmp
}

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
}

struct Stack: Container {
    typealias Item = T
    private var items: [T] = []
    mutating func append(_ item: T) { items.append(item) }
    var count: Int { items.count }
}

Error Handling

Functions that can throw errors are marked throws. Callers use try, try?, or try! and catch errors with do-catch. Define error types by conforming to the Error protocol. Errors are not exceptions — they are values that represent recoverable failures.

enum FileError: Error {
    case notFound
    case permissionDenied
}

func readFile(_ path: String) throws -> String {
    guard FileManager.default.fileExists(atPath: path) else {
        throw FileError.notFound
    }
    return try String(contentsOfFile: path)
}

do {
    let content = try readFile("/data.txt")
    print(content)
} catch FileError.notFound {
    print("not found")
} catch {
    print(error)
}

Frequently Asked Questions

Struct vs class?

Structs are value types (copied on assignment), classes are reference types (shared). Structs have no inheritance but can adopt protocols.

What is optional chaining?

Using ?. to call methods or access properties on an optional. Returns nil if the optional is nil instead of crashing.

When to use weak vs unowned?

weak when the reference can become nil (outlets, delegates). unowned when you know the reference outlives the referencing instance.

What does @escaping mean?

Marks a closure parameter that outlives the function scope, typically stored for later execution (completion handlers).

Originally published on Ayodhyyya. Last updated June 1, 2026.