struct — value type
Classes vs structs in modern Swift: value types vs reference types, properties, methods, initializers, inheritance and when to use each.
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.