0
0
Swiftprogramming~15 mins

Reference sharing and side effects in Swift - Deep Dive

Choose your learning style9 modes available
Overview - Reference sharing and side effects
What is it?
Reference sharing happens when two or more variables point to the same object in memory. This means changes made through one variable affect the others because they share the same data. Side effects occur when a change in one place unexpectedly changes data elsewhere, often due to reference sharing.
Why it matters
Without understanding reference sharing, you might accidentally change data in one part of your program and cause bugs in another. This can make your app behave unpredictably and be hard to fix. Knowing how references work helps you write safer, clearer code that avoids hidden surprises.
Where it fits
Before this, you should know about variables, constants, and basic data types in Swift. After this, you can learn about value types versus reference types, memory management, and how to use Swift’s copy-on-write behavior to control side effects.
Mental Model
Core Idea
When variables share a reference to the same object, changing the object through one variable changes it for all variables pointing to it.
Think of it like...
Imagine a group of friends sharing the same notebook. If one friend writes a note, everyone else sees that change because they all look at the same notebook.
┌─────────────┐       ┌─────────────┐
│ Variable A  │──────▶│   Object    │
└─────────────┘       └─────────────┘
                       ▲
┌─────────────┐        │
│ Variable B  │────────┘
└─────────────┘

Both variables point to the same object.
Build-Up - 7 Steps
1
FoundationUnderstanding Variables and References
🤔
Concept: Variables can hold references to objects, not just simple values.
In Swift, classes create reference types. When you assign an instance of a class to a variable, the variable holds a reference to that object in memory, not a copy of the object itself.
Result
Assigning one variable to another copies the reference, so both variables point to the same object.
Understanding that variables can share references is the first step to grasping how changes in one place affect others.
2
FoundationValue Types vs Reference Types
🤔
Concept: Swift has two main kinds of types: value types and reference types.
Value types (like structs and enums) copy their data when assigned or passed around. Reference types (classes) share the same instance when assigned or passed.
Result
Changing a value type copy does not affect the original, but changing a reference type affects all references.
Knowing the difference helps predict when side effects will happen.
3
IntermediateHow Reference Sharing Causes Side Effects
🤔Before reading on: do you think changing a property on one variable affects another variable pointing to the same object? Commit to your answer.
Concept: When multiple variables share a reference, modifying the object through one variable changes it for all.
Example: class Box { var value: Int init(value: Int) { self.value = value } } let box1 = Box(value: 10) let box2 = box1 box2.value = 20 print(box1.value) // What is printed? Since box1 and box2 point to the same Box instance, changing box2.value changes box1.value too.
Result
The output is 20, showing the side effect of shared reference.
Understanding this behavior prevents bugs where data changes unexpectedly across your program.
4
IntermediateAvoiding Side Effects with Copying
🤔Before reading on: do you think copying a reference type creates a new independent object? Commit to your answer.
Concept: To avoid side effects, you can create a new copy of an object instead of sharing the reference.
Swift classes don’t copy automatically, but you can implement a method to clone objects: class Box { var value: Int init(value: Int) { self.value = value } func copy() -> Box { return Box(value: self.value) } } let box1 = Box(value: 10) let box2 = box1.copy() box2.value = 20 print(box1.value) // What is printed? Now box1 and box2 are separate objects.
Result
The output is 10, showing no side effect because of copying.
Knowing how to copy objects helps control when changes affect others.
5
IntermediateReference Sharing in Collections
🤔
Concept: Collections like arrays can hold references, causing side effects when mutated.
If you have an array of class instances, changing one instance affects all references to it: class Item { var name: String init(name: String) { self.name = name } } var list1 = [Item(name: "A"), Item(name: "B")] var list2 = list1 list2[0].name = "Changed" print(list1[0].name) // What is printed? Both lists share the same Item instances.
Result
The output is "Changed", showing side effects inside collections.
Recognizing reference sharing inside collections helps avoid unexpected mutations.
6
AdvancedCopy-on-Write to Manage Side Effects
🤔Before reading on: do you think Swift’s standard collections always share references or do they sometimes copy? Commit to your answer.
Concept: Swift uses copy-on-write (COW) to optimize performance and avoid side effects in value types like Array and Dictionary.
COW means the collection shares storage until you modify it, then it makes a copy. This hides reference sharing from you: var arr1 = [1, 2, 3] var arr2 = arr1 arr2.append(4) print(arr1) // What is printed? arr1 stays unchanged because arr2 copied storage on append.
Result
[1, 2, 3] is printed, showing no side effect.
Understanding COW explains why some value types behave like references internally but avoid side effects.
7
ExpertUnexpected Side Effects with Shared Mutable State
🤔Before reading on: can two different parts of a program unknowingly share and mutate the same object? Commit to your answer.
Concept: Shared mutable state can cause bugs that are hard to find because changes in one place affect others silently.
Example: class Settings { var theme: String = "Light" } let globalSettings = Settings() func updateTheme() { let localSettings = globalSettings localSettings.theme = "Dark" } updateTheme() print(globalSettings.theme) // What is printed? The globalSettings changed unexpectedly because localSettings shared the reference.
Result
"Dark" is printed, showing a hidden side effect.
Knowing this helps design safer code by minimizing shared mutable objects or using immutability.
Under the Hood
Swift stores class instances on the heap and variables hold references (pointers) to that memory. Assigning a class instance copies the reference, not the object. When you mutate the object through any reference, the change is visible through all references because they point to the same memory location.
Why designed this way?
This design balances performance and flexibility. Copying large objects every time would be slow and memory-heavy. Sharing references allows efficient data handling but requires care to avoid unintended side effects.
┌───────────────┐
│   Variable A  │
│  (reference)  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│   Object in   │
│    Heap       │
│  (mutable)    │
└───────────────┘
       ▲
       │
┌──────┴────────┐
│   Variable B  │
│  (reference)  │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does assigning one class instance to another create a new copy? Commit to yes or no.
Common Belief:Assigning one class instance to another creates a new independent copy.
Tap to reveal reality
Reality:Assignment copies the reference, so both variables point to the same object.
Why it matters:Believing this causes bugs where changes to one variable unexpectedly affect another.
Quick: Do value types like structs ever share references internally? Commit to yes or no.
Common Belief:Value types always copy data and never share references.
Tap to reveal reality
Reality:Some value types use copy-on-write internally, sharing storage until mutated.
Why it matters:Ignoring this can lead to confusion about performance and mutation behavior.
Quick: Can side effects happen only with reference types? Commit to yes or no.
Common Belief:Side effects only happen with reference types because value types are safe.
Tap to reveal reality
Reality:Side effects can happen with value types if they contain reference type properties or use shared storage.
Why it matters:Overlooking this leads to unexpected bugs in complex data structures.
Quick: Does copying a reference type always create a deep copy? Commit to yes or no.
Common Belief:Copying a reference type variable creates a deep copy of the object.
Tap to reveal reality
Reality:Copying a reference type variable copies only the reference, not the object itself.
Why it matters:This misconception causes developers to mistakenly think they have independent objects, leading to hidden bugs.
Expert Zone
1
Swift’s copy-on-write optimization means some value types behave like references internally but maintain value semantics externally.
2
Mutable shared references can cause race conditions in concurrent code if not properly synchronized.
3
Implementing custom copying (deep copy) requires careful design to avoid partial or shallow copies that cause subtle bugs.
When NOT to use
Avoid relying on reference sharing when you need immutable data or thread safety. Instead, use value types or immutable patterns. For shared mutable state, consider synchronization tools like locks or actors.
Production Patterns
In production, developers use immutable value types for safety and reference types for shared resources. Copy-on-write collections balance performance and safety. Deep copying is used when isolation is critical, such as in undo systems or multi-threaded environments.
Connections
Immutability
Opposite approach to avoid side effects by preventing changes to shared data.
Understanding reference sharing highlights why immutability is a powerful tool to prevent bugs caused by unexpected mutations.
Memory Management
Reference sharing relies on how memory is allocated and managed for objects.
Knowing how Swift manages heap memory and references helps understand performance and lifecycle of shared objects.
Shared Mutable State in Concurrency (Computer Science)
Reference sharing creates shared mutable state, a core challenge in concurrent programming.
Recognizing reference sharing’s role in shared state helps grasp why concurrency bugs happen and how to avoid them.
Common Pitfalls
#1Unexpected mutation of shared object causing bugs.
Wrong approach:class Person { var name: String init(name: String) { self.name = name } } let p1 = Person(name: "Alice") let p2 = p1 p2.name = "Bob" print(p1.name) // Prints "Bob" unexpectedly
Correct approach:class Person { var name: String init(name: String) { self.name = name } func copy() -> Person { return Person(name: self.name) } } let p1 = Person(name: "Alice") let p2 = p1.copy() p2.name = "Bob" print(p1.name) // Prints "Alice" as expected
Root cause:Misunderstanding that assigning class instances copies references, not objects.
#2Assuming value types never cause side effects.
Wrong approach:struct Wrapper { var box: Box } var w1 = Wrapper(box: Box(value: 5)) var w2 = w1 w2.box.value = 10 print(w1.box.value) // Prints 10 unexpectedly
Correct approach:Make Box a struct or implement copying to avoid shared mutable reference: struct Box { var value: Int } var w1 = Wrapper(box: Box(value: 5)) var w2 = w1 w2.box.value = 10 print(w1.box.value) // Prints 5 as expected
Root cause:Value type contains a reference type property, causing hidden shared mutation.
#3Modifying shared collection elements without copying.
Wrong approach:class Item { var count: Int init(count: Int) { self.count = count } } var arr1 = [Item(count: 1)] var arr2 = arr1 arr2[0].count = 5 print(arr1[0].count) // Prints 5 unexpectedly
Correct approach:Copy elements before modifying: var arr1 = [Item(count: 1)] var arr2 = arr1.map { Item(count: $0.count) } arr2[0].count = 5 print(arr1[0].count) // Prints 1 as expected
Root cause:Collections hold references; copying the collection does not copy the objects inside.
Key Takeaways
Reference sharing means multiple variables point to the same object, so changes through one affect all.
Swift’s value types copy data, while reference types share instances, leading to different mutation behaviors.
Side effects happen when shared references are mutated unexpectedly, causing bugs that are hard to track.
Copying objects or using value types helps avoid unwanted side effects and makes code safer.
Understanding copy-on-write and shared mutable state is key to writing efficient and correct Swift programs.