Swift Structures vs Classes
Introduction
In Swift, both structures (struct) and classes (class) are fundamental building blocks that allow you to create custom data types. While they share many similarities, their underlying behavior and use cases differ significantly. Understanding when to use each is crucial for writing efficient, maintainable Swift code.
This guide explores the key differences between structures and classes, helping you make informed decisions in your Swift programming journey.
Key Differences at a Glance
Before diving into detailed explanations, here's a quick overview of the main differences:
| Feature | Structures | Classes | 
|---|---|---|
| Type | Value type | Reference type | 
| Inheritance | No inheritance | Supports inheritance | 
| Initialization | Auto-generated memberwise initializers | No default memberwise initializers | 
| Memory Management | Automatically handled | Uses ARC (Automatic Reference Counting) | 
| Mutability | Requires mutatingkeyword for methods that change properties | No special keyword required | 
| Identity operators | Cannot use ===and!== | Can use identity operators | 
Value Types vs Reference Types
The most fundamental difference between structures and classes is how they're handled in memory.
Structures: Value Types
When you assign a structure to a variable or pass it to a function, Swift creates a copy of the structure.
struct Point {
    var x: Int
    var y: Int
}
var point1 = Point(x: 10, y: 20)
var point2 = point1  // Creates a copy
point2.x = 30
print("Point 1: (\(point1.x), \(point1.y))")  // Output: Point 1: (10, 20)
print("Point 2: (\(point2.x), \(point2.y))")  // Output: Point 2: (30, 20)
As you can see, changing point2 doesn't affect point1 because point2 is a completely separate copy.
Classes: Reference Types
When you assign a class instance to a variable or pass it to a function, Swift passes a reference to the same instance, not a copy.
class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}
var person1 = Person(name: "John")
var person2 = person1  // Both variables now refer to the same instance
person2.name = "Jane"
print("Person 1: \(person1.name)")  // Output: Person 1: Jane
print("Person 2: \(person2.name)")  // Output: Person 2: Jane
Changing person2 also changes person1 because both variables reference the same instance in memory.
Inheritance
Classes Support Inheritance
Classes can inherit properties, methods, and other characteristics from another class:
class Vehicle {
    var currentSpeed = 0.0
    
    func description() -> String {
        return "traveling at \(currentSpeed) miles per hour"
    }
}
class Bicycle: Vehicle {
    var hasBasket = false
    
    override func description() -> String {
        return "a bicycle \(super.description())"
    }
}
let bike = Bicycle()
bike.currentSpeed = 15.0
print(bike.description())  // Output: a bicycle traveling at 15.0 miles per hour
Structures Don't Support Inheritance
Structures cannot inherit from other structures. However, they can adopt protocols, which offers a different way to share functionality:
protocol Movable {
    var speed: Double { get set }
    func move()
}
struct Car: Movable {
    var speed: Double = 0.0
    
    func move() {
        print("Car is moving at \(speed) mph")
    }
}
var myCar = Car()
myCar.speed = 55.0
myCar.move()  // Output: Car is moving at 55.0 mph
Initialization Differences
Structures: Auto-generated Memberwise Initializers
Swift automatically provides memberwise initializers for structures:
struct Rectangle {
    var width: Double
    var height: Double
}
// Swift automatically creates this initializer
let rectangle = Rectangle(width: 10.0, height: 5.0)
Classes: No Default Memberwise Initializers
Classes don't receive automatic memberwise initializers. You must provide your own initializers:
class Rectangle {
    var width: Double
    var height: Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
}
let rectangle = Rectangle(width: 10.0, height: 5.0)
Mutability and the mutating Keyword
Structures Require mutating Keyword
When you need to modify a structure's property within a method, you must mark the method as mutating:
struct Counter {
    var count = 0
    
    // This requires 'mutating' because it changes a property
    mutating func increment() {
        count += 1
    }
    
    // This doesn't need 'mutating' because it doesn't change any properties
    func currentCount() -> Int {
        return count
    }
}
var counter = Counter()
counter.increment()
print(counter.currentCount())  // Output: 1
Classes Don't Require Special Handling
Classes don't need special handling for methods that modify properties:
class Counter {
    var count = 0
    
    // No 'mutating' keyword needed
    func increment() {
        count += 1
    }
    
    func currentCount() -> Int {
        return count
    }
}
let counter = Counter()
counter.increment()
print(counter.currentCount())  // Output: 1
Note: You can modify the properties of a class instance even if the instance is stored in a constant (let) variable, because the reference remains constant but the content can change.
Identity Operators
Classes support identity operators (=== and !==), which check if two references point to the exact same instance:
class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}
let person1 = Person(name: "John")
let person2 = Person(name: "John")
let person3 = person1
print(person1 === person2)  // Output: false (different instances)
print(person1 === person3)  // Output: true (same instance)
Structures don't have identity operators because each structure instance is a unique copy with its own identity.
Memory Management
Classes Use ARC (Automatic Reference Counting)
Swift uses ARC to track and manage class instances. This can lead to concerns like reference cycles:
class Person {
    var name: String
    var apartment: Apartment?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}
class Apartment {
    var unit: String
    weak var tenant: Person?  // Using 'weak' to avoid strong reference cycle
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}
// Creating a scope to demonstrate memory management
do {
    let john = Person(name: "John")
    let apartment = Apartment(unit: "4A")
    
    john.apartment = apartment
    apartment.tenant = john
}  // Both objects will be deinitialized when the scope ends
// Output:
// John is being deinitialized
// Apartment 4A is being deinitialized
Structures Have Simpler Memory Management
Since structures are value types, they're automatically deallocated when they go out of scope, with no need for reference counting.
When to Use Structures vs Classes
Use Structures When:
- You need a simple data type to represent a value
- You want copies to be unique and separate
- The data will be used in a multi-threaded environment
- You want to avoid inheritance
- The data is relatively small in size
Real-world examples:
- Geometric shapes (Point, Size, Rect)
- Mathematical values (Vector, Matrix)
- Value-based models (Currency, Temperature)
struct Temperature {
    var celsius: Double
    
    var fahrenheit: Double {
        return celsius * 9/5 + 32
    }
    
    var kelvin: Double {
        return celsius + 273.15
    }
}
var currentTemp = Temperature(celsius: 25)
print("It's \(currentTemp.celsius)°C or \(currentTemp.fahrenheit)°F today.")
// Output: It's 25.0°C or 77.0°F today.
Use Classes When:
- You need to control identity (reference semantics)
- You need inheritance
- You need to use deinitializers
- You're working with Objective-C interoperability
- You're modeling a complex system with shared state
Real-world examples:
- UIView and its subclasses in UIKit
- File handles or network connections
- Complex data models that need inheritance
class BankAccount {
    let accountNumber: String
    var balance: Double
    let owner: String
    
    init(accountNumber: String, initialBalance: Double, owner: String) {
        self.accountNumber = accountNumber
        self.balance = initialBalance
        self.owner = owner
    }
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) -> Bool {
        guard balance >= amount else {
            return false
        }
        balance -= amount
        return true
    }
    
    deinit {
        print("Account \(accountNumber) is being closed")
    }
}
// Bank system example - multiple references to the same account
let johnsAccount = BankAccount(accountNumber: "12345", initialBalance: 1000, owner: "John")
// Bank teller and ATM both reference the same account
let tellerAccess = johnsAccount
let atmAccess = johnsAccount
tellerAccess.deposit(amount: 500)
print(atmAccess.balance)  // Output: 1500.0 - reflected across all references
Practical Example: Building an App
Let's examine how structures and classes might be used together in a simple note-taking app:
// Note content is a value type - perfect for a struct
struct NoteContent {
    var title: String
    var text: String
    var creationDate: Date
    var lastModified: Date
    
    mutating func update(newText: String) {
        text = newText
        lastModified = Date()
    }
}
// NotesManager handles operations and maintains state - better as a class
class NotesManager {
    var notes: [NoteContent] = []
    
    func createNote(title: String, text: String) {
        let now = Date()
        let newNote = NoteContent(title: title, text: text, creationDate: now, lastModified: now)
        notes.append(newNote)
    }
    
    func deleteNote(at index: Int) {
        guard index < notes.count else { return }
        notes.remove(at: index)
    }
    
    func updateNote(at index: Int, newText: String) {
        guard index < notes.count else { return }
        var note = notes[index]
        note.update(newText: newText)
        notes[index] = note
    }
    
    func getAllNotes() -> [NoteContent] {
        return notes
    }
}
// Usage example
let manager = NotesManager()
manager.createNote(title: "Shopping List", text: "Milk, Eggs, Bread")
manager.createNote(title: "Task List", text: "Learn Swift, Build App")
// Update a note
manager.updateNote(at: 0, newText: "Milk, Eggs, Bread, Cheese")
// Display all notes
for (index, note) in manager.getAllNotes().enumerated() {
    print("Note \(index + 1): \(note.title)")
    print("Content: \(note.text)")
    print("Last modified: \(note.lastModified)")
    print("------------------")
}
In this example:
- NoteContentis a structure because it represents a value (the content of a note)
- NotesManageris a class because it maintains state and handles operations on the notes collection
Performance Considerations
Understanding the performance implications can help you make better decisions:
- 
Structures generally have better performance for small to medium-sized data because: - Allocation is often faster (stack vs heap)
- No reference counting overhead
- Better memory locality
 
- 
Classes can be more efficient when: - Objects are large and would be expensive to copy
- Objects are frequently passed around but rarely modified
- Shared state is needed
 
Summary
Understanding the differences between structures and classes is fundamental to writing effective Swift code:
- 
Structures are value types that create copies when assigned or passed. They're great for representing simple data that stands on its own and doesn't need inheritance. 
- 
Classes are reference types that share a single instance when assigned or passed. They support inheritance and are ideal for complex objects that maintain state or need identity. 
Apple's recommendation is to use structures by default and switch to classes when you specifically need reference semantics, inheritance, or other class features.
Practice Exercises
- Create a structure to represent a playing card with suit and rank properties.
- Create a class to represent a deck of cards that can shuffle and deal cards.
- Experiment with a Personstruct vs aPersonclass and observe the different behaviors when you copy instances.
- Create a simple game that uses both structures and classes appropriately.
Additional Resources
- Swift Programming Language Guide: Classes and Structures
- WWDC 2015: Building Better Apps with Value Types in Swift
- Swift by Sundell: Reference and Value Types in Swift
- Apple Developer Documentation: Choosing Between Structures and Classes
Happy coding with Swift structures and classes!
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!