Skip to Content
Course content

223: Common Swift Interview Questions on Value Semantics Revisited

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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")") 
Rating
0 0

There are no comments for now.

to be the first to leave a comment.