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.