Skip to Content
Course content

16: Properties and Methods

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

I want to start today with a snippet of code that I've seen trip up almost every developer I've mentored when they first dive into Swift structs. You’re building a simple bank account tracker, and you’ve got a method to add money. It looks perfectly logical, but when you hit build, Xcode screams at you.

struct BankAccount {
    var balance: Double = 0.0
    
    func deposit(amount: Double) {
        balance += amount // ❌ Error: Cannot assign to property: 'self' is immutable
    }
}

The "Cannot assign to property" Headache

At first glance, this looks wrong. You defined `balance` as a `var`, so it should be changeable, right? The problem isn't the variable; it's the nature of the struct itself. In Swift, structs are value types. By default, the methods inside a struct aren't allowed to modify the properties of that struct because the instance itself is considered immutable when passed around.

If you were using a class, this would work fine. But with a struct, Swift forces you to be explicit about which methods change the internal state. This is actually a huge win for safety—it prevents you from accidentally changing data in a place where you thought you were just reading it.

Adding the Mutating Keyword

To fix this, we use the mutating keyword. This tells the compiler, "Hey, this specific method is going to change the data inside this struct."

struct BankAccount {
    var balance: Double = 0.0
    
    mutating func deposit(amount: Double) {
        balance += amount // ✅ Now this works!
    }
}

Now, the compiler is happy. I usually suggest thinking of mutating as a warning label for other developers reading your code. It signals that calling this method will change the object's state.

Logic Without Storage

Now that we can change data, let's talk about how we read it. Most of the time, you'll use stored properties (like balance), which just hold a value. But sometimes you need a property that calculates something on the fly. That's where computed properties come in.

Imagine you want to know if an account is overdrawn. You could create a stored property for isOverdrawn, but then you'd have to remember to update it every single time the balance changes. That's a recipe for bugs. Instead, do this:

struct BankAccount {
    var balance: Double = 0.0
    
    // This is a computed property
    var isOverdrawn: Bool {
        return balance < 0
    }
    
    mutating func deposit(amount: Double) {
        balance += amount
    }
}

Notice there's no = sign after isOverdrawn. It doesn't store a value in memory; it just runs that block of code every time you ask for the value. It's clean, it's efficient, and it's always accurate.

Organizing Behavior with Methods

Methods are essentially the "verbs" of your object. While properties describe what the object is, methods describe what the object does. A good rule of thumb I follow is: if the logic requires more than a simple calculation or a true/false check, it belongs in a method, not a computed property.

For example, withdrawing money isn't just about subtraction; it's about checking if you have enough funds first. That's a behavior, so it's a perfect candidate for a method:

struct BankAccount {
    var balance: Double = 0.0
    
    var isOverdrawn: Bool {
        return balance < 0
    }
    
    mutating func deposit(amount: Double) {
        balance += amount
    }
    
    mutating func withdraw(amount: Double) -> Bool {
        if balance >= amount {
            balance -= amount
            return true // Success
        } else {
            return false // Insufficient funds
        }
    }
}

By grouping this logic inside the BankAccount struct, you've ensured that no matter where this struct is used in your app, the rules for withdrawing money are always enforced in one single place.




📋 Practical Task

Building a SmartLight Controller

Your task is to create a SmartLight struct that manages the state of a smart bulb. This will exercise your ability to use stored properties, computed properties, and mutating methods.

Requirements:

  • Stored Properties:
    • brightness (an Integer from 0 to 100)
    • isOn (a Boolean)
  • Computed Property:
    • statusDescription: Returns a string. If the light is off, return "The light is off". If it's on and brightness is 0, return "The light is on but dim". Otherwise, return "The light is on at \(brightness)%".
  • Mutating Methods:
    • togglePower(): Flips the isOn value.
    • setBrightness(to level: Int): Updates the brightness property, but ensures the value stays between 0 and 100 (cap it at 100 if the input is higher, and floor it at 0 if the input is lower).

Testing your code: Create an instance of SmartLight, call setBrightness(to: 150), toggle the power, and print the statusDescription to the console to verify everything works as expected.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.