Skip to Content
Course content

61: Practice Exercise: Refactoring Class-Based Code into Value Types

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

Imagine you and a colleague are collaborating on a project. If you both have a link to the same Google Doc, you're dealing with a reference. When your colleague deletes a paragraph, it disappears for you too, instantly. That's how classes work in Swift. Now, imagine instead that you emailed them a PDF version of that document. They can highlight it, scribble in the margins, or even delete the whole thing, and your original file remains untouched. That's a value type, like a struct.

In a large codebase, the "Google Doc" approach (classes) can be a nightmare. You pass a user object to a function, that function changes the user's name for some internal reason, and suddenly your UI is displaying the wrong name elsewhere because every part of your app was pointing to that one single instance. I've spent more hours than I'd like to admit hunting down bugs caused by this kind of shared state.

The Danger of the Shared Document

Let's look at a typical mistake. Suppose we have a PlayerCharacter defined as a class. Because it's a reference type, assigning it to a new variable doesn't create a new character; it just creates another pointer to the same person.

class PlayerCharacter {
    var name: String
    var health: Int
    
    init(name: String, health: Int) {
        self.name = name
        self.health = health
    }
}

var hero = PlayerCharacter(name: "Arin", health: 100)
var backupHero = hero // This is NOT a copy. It's a reference.

backupHero.health = 50
print(hero.health) // Output: 50. Wait, what?

You probably intended for backupHero to be a snapshot of the hero's state, but instead, you accidentally damaged the original. This is where the "Value Type" mindset saves you.

Tearing Down the Reference

To fix this, we refactor the class into a struct. But it's not as simple as just changing the word class to struct. You have to think about how the data changes. Since structs are value types, any method that modifies a property must be marked with the mutating keyword. This tells Swift that the method will replace the old value with a new one.

struct PlayerCharacter {
    var name: String
    var health: Int
    
    // We don't need the explicit init here; Swift gives us a memberwise one for free.
    
    mutating func takeDamage(_ amount: Int) {
        health -= amount
    }
}

var hero = PlayerCharacter(name: "Arin", health: 100)
var backupHero = hero // Now this is a genuine copy.

backupHero.takeDamage(50)
print(hero.health) // Output: 100. Perfect.

Notice how the behavior changed? By switching to a struct, we've ensured that hero and backupHero are completely independent. If you change one, the other doesn't care.

When to Stick With the Refactor

I'll be honest: there are times when you actually want a class (like when you're managing a single, shared database connection or a hardware sensor). But for data models—things like Users, Settings, Coordinates, or Game Stats—structs should be your default. They are faster, they are thread-safe by nature, and they eliminate the "who changed my variable?" mystery that plagues reference-heavy code.




📋 Practical Task

Exercise: Converting a Shared Shopping Cart to an Independent Value Type

You've been handed a piece of legacy code where a ShoppingCart is implemented as a class. This is causing bugs because when a user "clones" their cart to compare items, changing the clone is accidentally modifying the original cart.

Your Task:

  • Refactor the ShoppingCart class into a struct.
  • Remove the manual init (let Swift's memberwise initializer handle it).
  • Mark the addItem and clearCart methods as mutating so the code compiles.
  • Test your refactor by creating a cart, assigning it to a second variable, adding an item to the second variable, and verifying that the first cart remains empty.
// STARTING CODE
class ShoppingCart {
    var items: [String]
    
    init(items: [String]) {
        self.items = items
    }
    
    func addItem(_ item: String) {
        items.append(item)
    }
    
    func clearCart() {
        items.removeAll()
    }
}

var mainCart = ShoppingCart(items: [])
var comparisonCart = mainCart
comparisonCart.addItem("Mechanical Keyboard")

print("Main Cart: \(mainCart.items)") // Currently prints ["Mechanical Keyboard"]
print("Comparison Cart: \(comparisonCart.items)") // Currently prints ["Mechanical Keyboard"]
Rating
0 0

There are no comments for now.

to be the first to leave a comment.