0
0
Swiftprogramming~15 mins

Why classes exist alongside structs in Swift - Why It Works This Way

Choose your learning style9 modes available
Overview - Why classes exist alongside structs
What is it?
In Swift, both classes and structs are ways to create custom data types that group related values and behaviors. Structs are simple, value-based types that copy their data when assigned or passed around. Classes are reference-based types that share a single instance when assigned or passed. They exist side by side because they serve different needs in how data is stored and shared.
Why it matters
Without having both classes and structs, programmers would lose flexibility in managing data. Structs make it easy to work with independent copies, which is safer and simpler. Classes allow shared, mutable state and inheritance, which are essential for many complex designs. Without classes, many real-world problems involving shared objects and identity would be harder to solve.
Where it fits
Before this, learners should understand basic Swift types and how variables store data. After this, they can learn about memory management, reference counting, and advanced object-oriented design patterns in Swift.
Mental Model
Core Idea
Structs copy data to create independent values, while classes share a single instance through references.
Think of it like...
Think of structs like photocopies of a document—each copy is separate and changes to one don't affect others. Classes are like sharing a single original document—everyone sees the same changes because they all refer to that one document.
┌─────────────┐       copy       ┌─────────────┐
│   Struct A  │ ───────────────▶ │   Struct B  │
│ value: 10  │                 │ value: 10  │
└─────────────┘                 └─────────────┘

┌─────────────┐       ref        ┌─────────────┐
│   Class A   │ ───────────────▶ │   Class B   │
│ value: 10  │                 │ value: 10  │
└─────────────┘                 └─────────────┘

Changes to Struct B don't affect Struct A.
Changes to Class B affect Class A because they share the same instance.
Build-Up - 7 Steps
1
FoundationUnderstanding Structs as Value Types
🤔
Concept: Structs create independent copies of data when assigned or passed.
In Swift, a struct holds its data directly. When you assign a struct to a new variable or pass it to a function, Swift makes a full copy. This means changes to the new copy don't affect the original. Example: struct Point { var x: Int var y: Int } var p1 = Point(x: 1, y: 2) var p2 = p1 p2.x = 10 print(p1.x) // 1 print(p2.x) // 10
Result
p1.x remains 1, showing that p2 is a separate copy.
Understanding that structs copy data helps you predict when changes affect only one instance, making code safer and easier to reason about.
2
FoundationUnderstanding Classes as Reference Types
🤔
Concept: Classes share a single instance through references instead of copying data.
In Swift, a class instance is stored in memory and variables hold references to it. Assigning a class instance to a new variable copies the reference, not the data. Changes through any reference affect the same shared instance. Example: class PointClass { var x: Int var y: Int init(x: Int, y: Int) { self.x = x self.y = y } } var c1 = PointClass(x: 1, y: 2) var c2 = c1 c2.x = 10 print(c1.x) // 10 print(c2.x) // 10
Result
Both c1.x and c2.x are 10, showing they refer to the same object.
Knowing classes share references explains why changes through one variable affect all others pointing to the same instance.
3
IntermediateWhy Structs Are Safer for Simple Data
🤔Before reading on: do you think structs or classes are safer for simple data? Commit to your answer.
Concept: Structs avoid unintended side effects by copying data, making them safer for simple, independent values.
Because structs copy their data, you don't have to worry about one part of your code accidentally changing data used elsewhere. This makes structs ideal for simple data like points, sizes, or colors where each value should be independent. Example: var a = Point(x: 5, y: 5) var b = a b.x = 100 // a.x is still 5, no surprises.
Result
Changes to b do not affect a, preventing bugs from shared state.
Understanding this safety helps you choose structs for data that should not be shared or mutated unexpectedly.
4
IntermediateWhy Classes Support Shared, Mutable State
🤔Before reading on: do you think shared mutable state is easier with structs or classes? Commit to your answer.
Concept: Classes allow multiple references to the same instance, enabling shared, mutable state needed in many designs.
When you want different parts of your program to see and modify the same data, classes let you do that easily. For example, a game character object shared across different systems can be updated in one place and all references see the change. Example: class Player { var health: Int = 100 } var player1 = Player() var player2 = player1 player2.health = 50 // player1.health is also 50 because they share the same instance.
Result
Shared changes reflect across all references, enabling coordinated state.
Knowing classes enable shared mutable state helps you design systems where objects need identity and shared updates.
5
IntermediateClasses Enable Inheritance and Polymorphism
🤔
Concept: Classes support inheritance, letting you create hierarchies and override behavior, which structs do not.
Classes can inherit from other classes, allowing you to build on existing code and customize behavior. This is useful for modeling real-world relationships like animals, vehicles, or UI components. Example: class Animal { func sound() -> String { return "" } } class Dog: Animal { override func sound() -> String { return "Bark" } } let pet = Dog() print(pet.sound()) // Bark
Result
Dog inherits from Animal and customizes the sound method.
Understanding inheritance explains why classes are essential for flexible, reusable designs that structs cannot support.
6
AdvancedMemory Management Differences Between Classes and Structs
🤔Before reading on: do you think structs or classes require more complex memory management? Commit to your answer.
Concept: Classes use reference counting to manage memory, while structs are copied and managed automatically without reference counting.
Classes in Swift are managed by Automatic Reference Counting (ARC), which tracks how many references point to an instance and frees memory when none remain. Structs are value types copied on assignment, so they don't need ARC. This difference affects performance and lifecycle. Example: class MyClass {} var a: MyClass? = MyClass() var b = a // ARC count increases b = nil // ARC count decreases // When count hits zero, instance is deallocated.
Result
Classes require ARC to avoid memory leaks; structs do not.
Knowing this helps you understand performance trade-offs and why classes can cause memory issues if not handled carefully.
7
ExpertWhy Swift Provides Both: Design Philosophy and Trade-offs
🤔Before reading on: do you think Swift's choice to have both classes and structs is mainly for performance, safety, or flexibility? Commit to your answer.
Concept: Swift includes both classes and structs to balance safety, performance, and flexibility, giving developers tools for different needs.
Swift's designers wanted to encourage safe, predictable code with value types (structs) while still supporting complex object-oriented patterns with classes. Structs promote immutability and thread safety, while classes enable identity, inheritance, and shared mutable state. This dual approach lets developers pick the right tool for each job. Trade-offs: - Structs: safer, faster for small data, no inheritance - Classes: flexible, support inheritance, but more complex memory management This design avoids forcing one model on all problems.
Result
Developers can write safer code with structs and more complex designs with classes.
Understanding this design philosophy clarifies why both types coexist and how to choose between them effectively.
Under the Hood
Structs store data directly in the variable's memory space. When assigned or passed, Swift copies the entire data, creating independent instances. Classes store data on the heap, and variables hold references (pointers) to that heap memory. Swift uses Automatic Reference Counting (ARC) to track how many references point to a class instance and deallocates it when no references remain.
Why designed this way?
Swift was designed to encourage safe and efficient code. Value types (structs) avoid bugs from shared mutable state and are optimized by the compiler. Reference types (classes) provide necessary features like inheritance and shared identity. This separation allows Swift to combine the best of functional and object-oriented programming, unlike languages that use only one model.
Value Type (Struct) Assignment:
┌─────────────┐     copy     ┌─────────────┐
│  Variable A │───────────▶│  Variable B │
│  [Data]     │            │  [Data]     │
└─────────────┘            └─────────────┘

Reference Type (Class) Assignment:
┌─────────────┐     ref      ┌─────────────┐
│  Variable A │───────────▶│  Variable B │
│  [Pointer]  │            │  [Pointer]  │
└─────────────┘            └─────────────┘

Heap Memory:
┌─────────────────────────────┐
│        Class Instance        │
│          [Data]             │
└─────────────────────────────┘

ARC tracks pointers to heap and frees memory when count is zero.
Myth Busters - 4 Common Misconceptions
Quick: Do structs in Swift support inheritance like classes? Commit to yes or no.
Common Belief:Structs can inherit from other structs just like classes do.
Tap to reveal reality
Reality:Structs do not support inheritance; only classes can inherit and override behavior.
Why it matters:Believing structs support inheritance can lead to design mistakes and confusion when trying to reuse code or override methods.
Quick: When you assign a class instance to a new variable, does Swift copy the data or the reference? Commit to your answer.
Common Belief:Assigning a class instance creates a new copy of the data.
Tap to reveal reality
Reality:Assigning a class instance copies the reference, so both variables point to the same object.
Why it matters:Misunderstanding this causes bugs where changes through one variable unexpectedly affect another.
Quick: Are structs always more memory efficient than classes? Commit to yes or no.
Common Belief:Structs always use less memory than classes because they are simpler.
Tap to reveal reality
Reality:Structs can use more memory if they are large because they are copied on assignment, while classes store one instance shared by references.
Why it matters:Assuming structs are always more efficient can lead to performance issues when large structs are copied frequently.
Quick: Does using classes always mean your code is less safe than using structs? Commit to yes or no.
Common Belief:Classes are inherently unsafe because they allow shared mutable state.
Tap to reveal reality
Reality:Classes can be used safely with careful design and Swift's ARC helps manage memory automatically.
Why it matters:Avoiding classes altogether due to fear of unsafety limits design options and can complicate code unnecessarily.
Expert Zone
1
Classes can have deinitializers to run cleanup code when instances are deallocated, which structs cannot have.
2
Swift uses copy-on-write optimization for some standard library structs (like Array) to avoid expensive copies until mutation happens.
3
Reference cycles in classes can cause memory leaks if not broken with weak or unowned references, a subtle issue not present with structs.
When NOT to use
Use structs when data is simple, small, and should be independent copies. Avoid classes when inheritance or shared mutable state is unnecessary. For concurrency, prefer structs or immutable classes to avoid race conditions. When you need identity, polymorphism, or lifecycle management, classes are appropriate.
Production Patterns
In real-world Swift apps, structs are used for data models, configuration, and simple value types. Classes are used for UI components, controllers, and objects requiring inheritance or shared state. Developers often combine both, using structs for safety and classes for flexibility, balancing performance and design.
Connections
Functional Programming
Structs embody value semantics common in functional programming, emphasizing immutability and copying.
Understanding structs as value types connects to functional programming principles, helping write safer, side-effect-free code.
Memory Management in Operating Systems
Classes' use of reference counting parallels OS-level resource tracking and garbage collection concepts.
Knowing how ARC works in classes relates to broader memory management strategies, deepening understanding of resource lifecycles.
Human Identity vs Copies
Classes represent objects with identity like people, while structs represent copies like printed photos.
This connection to human concepts of identity versus copies helps grasp why some data needs shared references and others do not.
Common Pitfalls
#1Unexpected shared changes when using classes thinking they behave like structs.
Wrong approach:class Person { var name: String init(name: String) { self.name = name } } var p1 = Person(name: "Alice") var p2 = p1 p2.name = "Bob" print(p1.name) // Bob (unexpected for beginners)
Correct approach:struct Person { var name: String } var p1 = Person(name: "Alice") var p2 = p1 p2.name = "Bob" print(p1.name) // Alice (expected independent copy)
Root cause:Confusing reference semantics of classes with value semantics of structs leads to bugs from unintended shared mutations.
#2Trying to use inheritance with structs.
Wrong approach:struct Vehicle {} struct Car: Vehicle {} // Error: inheritance not supported
Correct approach:class Vehicle {} class Car: Vehicle {} // Correct inheritance
Root cause:Misunderstanding that structs do not support inheritance causes compile errors and design confusion.
#3Memory leaks from strong reference cycles in classes.
Wrong approach:class Node { var next: Node? init() {} } var a = Node() var b = Node() a.next = b b.next = a // creates cycle // Memory leak because ARC can't free nodes
Correct approach:class Node { weak var next: Node? init() {} } var a = Node() var b = Node() a.next = b b.next = a // no cycle, ARC frees memory
Root cause:Not using weak references in class cycles causes ARC to keep instances alive, leaking memory.
Key Takeaways
Structs and classes serve different purposes: structs copy data for safety, classes share references for flexibility.
Choosing between structs and classes affects how your data behaves when assigned or passed around.
Classes support inheritance and shared mutable state, which structs do not, enabling complex designs.
Swift's design balances safety and power by providing both types, letting you pick the best tool for each job.
Understanding memory management differences helps avoid bugs and performance issues in Swift programs.