cv.cm

Coding · Programming languages

Swift tutorial for beginners

7 min read

Short answer

Swift is a fast, safe, compiled language created by Apple for iPhone, iPad, Mac, Apple Watch and Apple TV apps, and it also runs on Linux and Windows. Save print("Hello, world") in hello.swift and run swift hello.swift. The core ideas are let constants and var variables, type inference, optionals for values that may be missing, structs and enums with value semantics, protocols, and errors handled with do/try/catch.

Open JSON formatter

What Swift is and what it is used for

Swift was introduced by Apple in 2014 as a modern replacement for Objective-C and was open-sourced in 2015; the project lives at swift.org. It is the main language for apps on Apple platforms, usually with the SwiftUI or UIKit frameworks. Swift is also used for server-side code (for example with the Vapor framework) and command-line tools. The language is designed to prevent common bugs: variables must be initialized, arrays check their bounds, and nil must be handled explicitly.

Install Swift and run Hello, world

On a Mac, install Xcode from the App Store or the Xcode command-line tools. On Linux and Windows, download a toolchain from swift.org. Check it with swift --version.

print("Hello, world")
swift hello.swift
# Hello, world
swiftc hello.swift -o hello   # compile to a binary
./hello

For a larger project, run swift package init --type executable and then swift run. Xcode playgrounds are another easy way to experiment.

Constants, variables and basic types

Use let for constants and var for variables. Prefer let; the compiler warns when a var is never changed. Types are inferred, and Swift never converts between numeric types implicitly.

TypeExampleNotes
Intlet n = 4264-bit on modern platforms
Doublelet x = 3.14Default floating-point type
Boollet ok = truetrue or false
Stringlet s = "hi \(name)"Unicode text, value type
Characterlet c: Character = "é"One user-perceived character
[Int], [String: Int][1, 2], ["a": 1]Array and dictionary
let name = "Ada"
var count = 7
count += 1
let avg = Double(count) / 2   // explicit conversion
print("\(name) has \(count) items, avg \(avg)")  // Ada has 8 items, avg 4.0
print(7 / 2, 7 % 2)          // 3 1

\(expression) inside a string literal inserts a value (string interpolation).

Control flow

let score = 72
if score >= 90 {
    print("A")
} else if score >= 70 {
    print("B")                 // B
} else {
    print("C")
}

for i in 0..<3 {
    print(i, terminator: " ")  // 0 1 2
}
print()

var n = 10
while n > 1 { n /= 2 }
print(n)                       // 1

let day = "SAT"
switch day {
case "SAT", "SUN":
    print("weekend")           // weekend
default:
    print("weekday")
}

switch must be exhaustive, does not fall through by default, and can match ranges (case 90...100:), tuples and enum cases.

Functions and closures

Parameters have argument labels that callers write, which makes calls read like sentences. Use _ to omit a label. Closures are unnamed functions written in braces.

func greet(_ name: String, with greeting: String = "Hello") -> String {
    return "\(greeting), \(name)!"
}

print(greet("Lin"))                  // Hello, Lin!
print(greet("Lin", with: "Hi"))      // Hi, Lin!

let nums = [3, 1, 2, 5]
let squares = nums.map { $0 * $0 }
let evens = nums.filter { $0 % 2 == 0 }
let sorted = nums.sorted(by: >)
print(squares, evens, sorted)        // [9, 1, 4, 25] [2] [5, 3, 2, 1]

$0 refers to the first closure argument. When a closure is the last argument, it can be written after the parentheses (trailing closure syntax).

Arrays, dictionaries and sets

var fruits = ["apple", "pear"]
fruits.append("fig")
print(fruits.count, fruits[0], fruits.contains("fig"))  // 3 apple true

var ages: [String: Int] = ["ann": 31, "bob": 25]
ages["cy"] = 40
for (person, age) in ages.sorted(by: { $0.key < $1.key }) {
    print(person, age)                                  // ann 31, bob 25, cy 40
}
print(ages["zed", default: 0])                          // 0

let tags: Set = ["b", "a", "b"]
print(tags.count)                                       // 2

Collections are value types: assigning an array to another variable creates an independent copy (copied lazily, only when one of them changes). A let array cannot be modified at all.

Signature feature: optionals

A value that may be missing has an optional type, written String?. You cannot use it as a String until you unwrap it, so the compiler forces you to handle the nil case.

let input = "42"
let number: Int? = Int(input)      // nil if input is not a number

if let number {                     // unwrap: runs only when not nil
    print("got \(number)")          // got 42
}

func firstWord(_ text: String) -> String? {
    text.split(separator: " ").first.map(String.init)
}

guard let word = firstWord("hello world") else {
    fatalError("no words")
}
print(word)                                  // hello
print(firstWord("")?.uppercased() ?? "none") // none

if let and guard let unwrap safely, ?. (optional chaining) calls a method only when the value exists, and ?? supplies a default. The force unwrap value! crashes when the value is nil, so use it rarely. The short form if let number needs Swift 5.7 or later.

Structs, classes, enums and protocols

Structs and enums are value types; classes are reference types with inheritance. Swift code favors structs and protocols (interfaces) over class hierarchies.

protocol Shape {
    func area() -> Double
}

struct Rect: Shape {
    var width: Double
    var height: Double
    func area() -> Double { width * height }
}

enum Circle: Shape {
    case unit
    case radius(Double)
    func area() -> Double {
        switch self {
        case .unit: return Double.pi
        case .radius(let r): return Double.pi * r * r
        }
    }
}

let shapes: [any Shape] = [Rect(width: 2, height: 3), Circle.radius(1)]
let total = shapes.reduce(0) { $0 + $1.area() }
print((total * 100).rounded() / 100)   // 9.14

Structs get a memberwise initializer automatically (Rect(width:height:)). Enum cases can carry associated values, as .radius(Double) does.

Error handling

Functions that can fail are marked throws. Callers must use try inside a do/catch block, or use try? to turn an error into nil.

enum AgeError: Error {
    case notANumber(String)
    case negative
}

func parseAge(_ text: String) throws -> Int {
    guard let age = Int(text) else { throw AgeError.notANumber(text) }
    guard age >= 0 else { throw AgeError.negative }
    return age
}

do {
    print(try parseAge("41"))          // 41
    print(try parseAge("abc"))
} catch AgeError.notANumber(let text) {
    print("not a number: \(text)")     // not a number: abc
} catch {
    print("other error: \(error)")
}

let maybe = try? parseAge("-3")        // nil
print(maybe as Any)                    // nil

A small complete program: word frequency

import Foundation

let args = CommandLine.arguments
guard args.count == 2 else {
    print("usage: swift words.swift FILE")
    exit(1)
}

do {
    let text = try String(contentsOfFile: args[1], encoding: .utf8).lowercased()
    let words = text.split { !$0.isLetter && $0 != "'" }
    var counts: [Substring: Int] = [:]
    for word in words {
        counts[word, default: 0] += 1
    }
    for (word, n) in counts.sorted(by: { $0.value > $1.value }).prefix(5) {
        print(String(n).padding(toLength: 6, withPad: " ", startingAt: 0) + word)
    }
} catch {
    print("cannot read \(args[1]): \(error.localizedDescription)")
    exit(1)
}

Run it with swift words.swift notes.txt. Foundation provides file reading and string helpers; it is available on Apple platforms and in the Linux and Windows toolchains.

Common beginner mistakes

  • Trying to change a let constant or a property of a let struct. Use var when the value must change.
  • Expecting a struct to be shared like an object. Changing a copy does not change the original; use a class if you need shared identity.
  • Indexing a String with an integer, such as s[0]. Use s.first, s.prefix(3) or String.Index values.
  • Mixing Int and Double in arithmetic without converting, which is a compile error.
  • Forgetting try before a throwing call, or using try!, which crashes on error.

Where to go next

Read "The Swift Programming Language" on docs.swift.org, the official guide and reference, and the documentation hub on swift.org. For apps, follow Apple's SwiftUI tutorials in Xcode. Then learn Swift concurrency (async/await and actors), generics, and the Swift Package Manager for dependencies.

FAQ

Is Swift only for Apple platforms?

No. Swift is open source and runs on Linux and Windows too, and it is used for server-side code and tools. However, app frameworks such as SwiftUI and UIKit are Apple-only.

Should I learn Swift or Objective-C?

Learn Swift. It is Apple's recommended language for new apps, and it can call existing Objective-C code when needed.

What is the difference between a struct and a class in Swift?

A struct is a value type that is copied when assigned or passed; a class is a reference type, so all variables share one instance. Classes also support inheritance and deinitializers.

What does the question mark after a type mean?

Int? is an optional: it holds either an Int or nil. You must unwrap it, for example with if let, before using the value.

All coding tutorials