-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
223: Common Swift Interview Questions on Value Semantics Revisited
If you've been prepping for Swift interviews, you've probably heard the mantra "structs are value types, classes are reference types" a thousand times. It sounds simple. But in a senior-level interview, that's usually the bait. The interviewer isn't looking for the definition; they're looking to see if you understand where that definition breaks down.
Let's get our hands dirty. I want to build a simple Document system. In a real app, a document might have a massive amount of text, so we don't want to copy the entire string every single time we pass the document around. I'll start with a basic struct.
The Illusion of Safety
struct Document {
var title: String
var content: String
}
var doc1 = Document(title: "Notes", content: "Hello World")
var doc2 = doc1
doc2.title = "Updated Notes"
print("Doc 1: \(doc1.title)") // Notes
print("Doc 2: \(doc2.title)") // Updated Notes
Everything is behaving as expected. I copied doc1 into doc2, changed doc2, and doc1 stayed put. This is the value semantics we're taught. It's clean. It's predictable. But here is where the "interview trap" begins. What happens if we optimize our Document to handle huge amounts of data by moving the content into a separate storage object?
When Structs Lie to You
Let's say I create a TextStorage class to hold the actual bytes of the document. I'll wrap that class inside my Document struct. I'm thinking, "I still have a struct, so I still have value semantics," right? Let's test that.
class TextStorage {
var text: String
init(_ text: String) { self.text = text }
}
struct Document {
var title: String
var storage: TextStorage
}
var doc1 = Document(title: "Notes", storage: TextStorage("Hello World"))
var doc2 = doc1
doc2.storage.text = "Changed Content"
print("Doc 1 content: \(doc1.storage.text)")
// Output: Doc 1 content: Changed Content
print("Doc 2 content: \(doc2.storage.text)")
// Output: Doc 2 content: Changed Content
Wait. That's not right. I used a struct, but changing doc2 just mutated doc1. This is the "aha!" moment. The Document struct itself was copied, but the storage property is just a reference to a class instance. Both structs are now pointing to the exact same object in memory.
In an interview, this is the exact point where you should stop and explain that value semantics are not guaranteed just because you used a struct. If your struct contains a reference type, you have "leaky" value semantics.
Implementing Manual Copy-on-Write
So, how do we fix this? We want the performance of a reference type (not copying the big string until we actually need to) but the behavior of a value type. This is exactly how Swift's Array and Dictionary work under the hood. It's called Copy-on-Write (COW).
I need to intercept the write operation. Instead of letting the user touch the TextStorage directly, I'll make it private and provide a subscript or a method that checks if the storage is uniquely referenced.
class TextStorage {
var text: String
init(_ text: String) { self.text = text }
}
struct Document {
var title: String
private var _storage: TextStorage
var content: String {
get { _storage.text }
set {
// This is the magic: isUnique
if !isUnique {
print("Copying storage to maintain value semantics...")
_storage = TextStorage(newValue)
} else {
_storage.text = newValue
}
}
}
private var isUnique: Bool {
// In a real scenario, we'd use Swift's internal reference counting
// but for this demonstration, we're simulating the logic.
// Since we can't easily access the raw ref count of a class in Swift,
// we'll simulate the 'copy if shared' logic.
return false // Let's assume it's shared for the demo
}
}
Wait, I just realized I can't actually check the reference count of a class instance in standard Swift. To truly implement COW, I usually have to wrap the reference in a way that I can manage. But the logic the interviewer wants to see is this: Check if the reference is shared; if it is, create a new copy of the reference before mutating.
Let's refine the logic. I'll use a simple "copy-on-mutation" approach by making the property setter handle the cloning.
struct Document {
var title: String
private var _storage: TextStorage
var content: String {
get { _storage.text }
set {
// To truly implement COW, we check if multiple
// variables point to this _storage.
// If they do, we clone the storage first.
_storage = TextStorage(newValue)
}
}
}
By forcing the creation of a new TextStorage instance whenever content is set, I've restored value semantics. Now, doc1 and doc2 can share the same storage for as long as they are only reading. The moment one of them tries to change the text, it gets its own private copy. That's the bridge between performance and safety.
📋 Practical Task
Fixing the Leaky Cache Store
You are reviewing a teammate's code. They've implemented a UserSession struct that uses a CacheStore class to hold session data. However, they noticed that updating the session in one part of the app is unexpectedly updating it everywhere else.
Your Task: Modify the UserSession struct to ensure it has true value semantics. You must ensure that when the sessionData property is modified, it does not affect other copies of the UserSession.
class CacheStore {
var data: [String: String]
init(data: [String: String]) {
self.data = data
}
}
struct UserSession {
var username: String
var store: CacheStore // This is the source of the leak!
// TODO: Implement a way to update session data
// without mutating other copies of UserSession.
mutating func updateData(key: String, value: String) {
store.data[key] = value
}
}
// Test Case:
var session1 = UserSession(username: "Alice", store: CacheStore(data: ["theme": "dark"]))
var session2 = session1
session2.updateData(key: "theme", value: "light")
// This currently prints "light", but it SHOULD print "dark"
print("Session 1 Theme: \(session1.store.data["theme"] ?? "none")") There are no comments for now.