Skip to main content

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:

FeatureStructuresClasses
TypeValue typeReference type
InheritanceNo inheritanceSupports inheritance
InitializationAuto-generated memberwise initializersNo default memberwise initializers
Memory ManagementAutomatically handledUses ARC (Automatic Reference Counting)
MutabilityRequires mutating keyword for methods that change propertiesNo special keyword required
Identity operatorsCannot 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.

swift
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.

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

  1. You need a simple data type to represent a value
  2. You want copies to be unique and separate
  3. The data will be used in a multi-threaded environment
  4. You want to avoid inheritance
  5. The data is relatively small in size

Real-world examples:

  • Geometric shapes (Point, Size, Rect)
  • Mathematical values (Vector, Matrix)
  • Value-based models (Currency, Temperature)
swift
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:

  1. You need to control identity (reference semantics)
  2. You need inheritance
  3. You need to use deinitializers
  4. You're working with Objective-C interoperability
  5. 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
swift
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:

swift
// 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:

  • NoteContent is a structure because it represents a value (the content of a note)
  • NotesManager is 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:

  1. 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
  2. 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

  1. Create a structure to represent a playing card with suit and rank properties.
  2. Create a class to represent a deck of cards that can shuffle and deal cards.
  3. Experiment with a Person struct vs a Person class and observe the different behaviors when you copy instances.
  4. Create a simple game that uses both structures and classes appropriately.

Additional Resources

Happy coding with Swift structures and classes!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)