Structs, Classes & Swift's Value/Reference Model

iOS Development Fundamentals

Chapter 4 · Structs, Classes & Swift's Value/Reference Model

This chapter closes out core Swift with its single most consequential design decision: structs are value types, classes are reference types. Getting this distinction genuinely intuitive now is what makes SwiftUI's own view code — starting next chapter — make sense as a coherent design, not a set of memorized rules.

Struct and Class Syntax Side by Side

struct Point { var x: Double var y: Double } class Counter { var value = 0 func increment() { value += 1 } }

The syntax looks almost identical — properties, methods, both support initializers. The real difference is invisible in the declaration itself, and only shows up the moment a value is assigned or passed around.

The Core Distinction: Copy vs. Share

var pointA = Point(x: 0, y: 0) var pointB = pointA // pointB is a real, independent COPY pointB.x = 100 print(pointA.x) // 0 — pointA was never touched print(pointB.x) // 100
let counterA = Counter() let counterB = counterA // counterB points at the SAME real instance counterB.increment() print(counterA.value) // 1 — the same underlying object changed print(counterB.value) // 1
struct — Value Type

Assigning or passing it creates a real, independent copy. No shared state, no surprises from one place changing another's data.

class — Reference Type

Assigning or passing it shares the same underlying instance. Changing it through one reference is visible through every other reference to it.

A Real, Important Fact
Swift's own standard library types you already use constantly — String, Array, Dictionary — are all real structs, not classes. Apple's own real, deliberate design choice for the language leans toward value types by default; classes are reached for specifically when shared, mutable identity is genuinely needed.

Mutating Methods

Because a struct instance's own properties can't change from inside a regular method (structs are immutable by default from within their own methods), a method that needs to modify self must be explicitly marked mutating:

struct Point { var x: Double var y: Double mutating func moveRight(by amount: Double) { x += amount } } var point = Point(x: 0, y: 0) point.moveRight(by: 10) print(point.x) // 10
A Real Consequence
A mutating method can only be called on a struct stored in a var, never a let — the compiler enforces this directly, since a let constant's own value is never allowed to change, and a mutating method genuinely replaces the whole instance's own value under the hood.

Identity vs. Equality

For classes, Swift distinguishes two real, different questions: are two references pointing at the exact same underlying instance (===), or do two instances just happen to hold equal values (==, which a type has to explicitly opt into via Equatable)?

let counterA = Counter() let counterB = counterA let counterC = Counter() print(counterA === counterB) // true — same real instance print(counterA === counterC) // false — different instances, even if values matched

Structs, being value types with no shared identity to begin with, only ever use == — and Swift can generate that conformance to Equatable automatically for a struct whose own properties are all themselves Equatable.

A First, Brief Look at ARC

Every class instance is managed by Automatic Reference Counting (ARC) — Swift keeps a real count of how many references currently point at a given instance, and deallocates it automatically once that count reaches zero. This is what makes the shared, reference-type behavior above safe to use without manual memory management. It's also, per Chapter 3's own closing note, the real source of strong reference cycles — two class instances each holding a strong reference to the other, which ARC can't resolve on its own. This course's own second course, Architecture & Data, covers weak and unowned references — the real fix — once classes are used more heavily for real app state.

Why SwiftUI Views Are Structs
This is the direct payoff of the entire chapter: SwiftUI's own View types (introduced in Chapter 1, covered fully from Chapter 5) are structs specifically because a view needs to be cheap to create and recreate — SwiftUI rebuilds view structs constantly as state changes, and copying a lightweight struct with no ARC bookkeeping involved is genuinely far cheaper than allocating and reference-counting a class instance every time.

Hands-On Exercises

Exercise 1

Define a struct Rectangle with var width: Double and var height: Double, and a mutating method scale(by factor: Double) that multiplies both. Create one instance, copy it to a second variable, scale only the copy, and print both instances' widths to confirm the original is unaffected.

📄 View solution
Exercise 2

Define a class ShoppingCart with var itemCount = 0 and a method addItem() that increments it. Create one instance, assign it to a second constant, call addItem() on the second, and print both constants' itemCount to confirm they show the same real, shared value.

📄 View solution
Exercise 3

Explain, in your own words, why SwiftUI deliberately builds its own View types as structs rather than classes, connecting your answer to both the copy-vs-share distinction and ARC.

📄 View solution

Chapter 4 Quick Reference

  • struct = value type — assigning/passing copies it; class = reference type — assigning/passing shares the same instance
  • String, Array, and Dictionary are all real structs in Swift's own standard library
  • A struct method that modifies self must be marked mutating, and can only be called on a var
  • === checks reference identity (classes only); == checks value equality (via Equatable)
  • Classes are managed by ARC, which is also the real source of strong reference cycles — covered with the real fix in Architecture & Data
  • SwiftUI's own View types are structs specifically because they need to be cheap to create and recreate constantly