SwiftThe Basics

struct — value type

Classes and structures are the building blocks you use to model your app’s data. In Swift both are very powerful, but they have a fundamental difference that changes how they behave: structures are value types and classes are reference types. Understanding this is key to writing correct, predictable code.

struct — value type

When you assign a structure to another variable or pass it to a function, an independent copy is created. Modifying the copy doesn’t affect the original. This predictable behavior is one of the reasons modern Swift prefers struct over class in most cases.

struct Point {
    var x: Double
    var y: Double
}

var origin = Point(x: 0, y: 0)
var destination = origin   // a COPY is created

destination.x = 10
destination.y = 10

print(origin.x)       // 0.0 — didn't change
print(destination.x)  // 10.0 — only the copy changed

class — reference type

When you assign a class to another variable, both point to the same object in memory. Modifying one affects the other. This can generate hard-to-track bugs if you’re not aware of it.

class Counter {
    var value: Int = 0
}

let counterA = Counter()
let counterB = counterA  // both point to THE SAME object

counterB.value = 100

print(counterA.value)  // 100 — changed because it's the same object!
print(counterB.value)  // 100

Properties

Both structs and classes can have stored properties (store a value) and computed properties (calculate a value from others).

struct Rectangle {
    // Stored properties
    var width: Double
    var height: Double

    // Computed property — doesn't store, calculates
    var area: Double {
        width * height
    }

    var perimeter: Double {
        2 * (width + height)
    }
}

let rect = Rectangle(width: 5, height: 3)
print(rect.area)       // 15.0
print(rect.perimeter)  // 16.0

Methods

Functions defined inside a type are called methods. In structs, methods that modify properties must be marked with mutating.

struct Stack {
    private var elements: [Int] = []

    // mutating because it modifies the struct
    mutating func push(_ element: Int) {
        elements.append(element)
    }

    mutating func pop() -> Int? {
        elements.popLast()
    }

    var isEmpty: Bool {
        elements.isEmpty
    }

    var count: Int {
        elements.count
    }
}

var stack = Stack()
stack.push(10)
stack.push(20)
stack.push(30)
print(stack.count)   // 3
print(stack.pop()!)  // 30
print(stack.count)   // 2

Initializers

Structs get a free memberwise initializer with all their properties. Classes need an explicit one if properties don’t have default values.

// Struct — free initializer
struct Product {
    var name: String
    var price: Double
    var available: Bool = true  // default value
}

let p1 = Product(name: "Book", price: 15.99)              // available = true
let p2 = Product(name: "Course", price: 49.99, available: false)

// Class — needs explicit init
class Session {
    var token: String
    var expiration: Date

    init(token: String, expiration: Date) {
        self.token = token
        self.expiration = expiration
    }
}

Inheritance — exclusive to class

Only classes support inheritance. A class can inherit properties and methods from another and override them with override. Structs don’t inherit — they use protocols instead.

class Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func sound() -> String {
        "..."
    }
}

class Dog: Animal {
    override func sound() -> String {
        "Woof"
    }
}

class Cat: Animal {
    override func sound() -> String {
        "Meow"
    }
}

let animals: [Animal] = [Dog(name: "Rex"), Cat(name: "Luna")]
for animal in animals {
    print("\(animal.name): \(animal.sound())")
}
// Rex: Woof
// Luna: Meow

When to use struct vs class?

Apple’s official guide and the Swift community are clear: always start with struct. Switch to class only when you need one of these characteristics:

  • Inheritance (you need one type to extend another)
  • Intentionally sharing mutable state across multiple parts of the code
  • Interoperability with Objective-C or APIs that require classes
  • Object lifecycle management with deinit

In SwiftUI, for example, data models are almost always structs. Classes appear in @Observable or ObservableObject that manage shared state.

// ✅ Data model — use struct
struct Article {
    let id: UUID
    var title: String
    var content: String
    var published: Bool
}

// ✅ Shared state in SwiftUI — use class with @Observable
import Observation

@Observable
class AppState {
    var userLoggedIn = false
    var notificationCount = 0
}

Summary

  • struct: value type, creates copies, predictable behavior — use it by default.
  • class: reference type, shares the same object — use it when you need inheritance or shared state.
  • Computed properties: derive their value from other properties, without storing.
  • mutating: required in struct methods that modify properties.
  • Memberwise initializer: structs get it for free; classes need explicit init.
  • Inheritance: exclusive to classes; structs use protocols instead.

With this you complete the fundamentals of modern Swift. The next step is exploring protocols, generics and concurrency with async/await — topics we’ll cover in the intermediate posts of the series.