Matua Doc

Methods: instance, static, and mutating

Learning intentions

In this lesson, you will learn how methods belong to a type and how that changes the way you design reusable code. Explain what a method is and why it is attached to a type, compare methods with open functions that are written outside any type, and choose the right approach based on what data the code needs to access.

You will also practise two important method keywords in Swift. Explain static methods for type-level behaviour, and explain mutating methods for safely changing struct state.

What is a method?

A method is a function written inside a type such as a struct, class, or enum, so it can work directly with that type's data and purpose. Methods live inside the model definition instead of being separate global code, so behaviour stays close to related properties and intent is easier to read.

struct Player {
    var name: String
    var score: Int

    func summary() -> String {
        return "\(name) has \(score) points."
    }
}

What is an open function?

An open function is a standalone function written outside a type, and it can still work with model data when that data is passed in as parameters. Open functions are called directly by name, and they receive all context through parameters such as arrays and struct instances.

struct Player {
    var name: String
    var score: Int
}

func averageScore(from scores: [Int]) -> Double {
    guard !scores.isEmpty else { return 0 }
    return Double(scores.reduce(0, +)) / Double(scores.count)
}

func playerSummary(for player: Player) -> String {
    return "\(player.name) has \(player.score) points."
}
let scores = [8, 12, 15, 9]
let p = Player(name: "Ari", score: 12)

print(averageScore(from: scores))
print(playerSummary(for: p))

Open functions are useful when behaviour is generic and not clearly owned by one type or when you want lightweight utility logic.

How methods can help

The same behaviour can often be moved into a type as methods, which makes usage cleaner because the value provides its own context. Methods reduce repeated parameter passing, and make behaviour easier to discover next to the data it belongs to.

struct ScoreBoard {
    var scores: [Int]

    func averageScore() -> Double {
        guard !scores.isEmpty else { return 0 }
        return Double(scores.reduce(0, +)) / Double(scores.count)
    }
}

struct Player {
    var name: String
    var score: Int

    func summary() -> String {
        return "\(name) has \(score) points."
    }
}
let board = ScoreBoard(scores: [8, 12, 15, 9])
let p = Player(name: "Ari", score: 12)

print(board.averageScore())
print(p.summary())

With methods, each model owns behaviour that belongs to it, so your API reads more naturally and scales better as projects grow.

Instance methods

An instance method is the most common method type, and it runs on one specific instance of a type. Call instance methods with dot syntax like player.summary(), and use them to read or compute from that instance's properties.

Instance methods are ideal when behaviour depends on one object's current state such as score, health, balance, or progress.

Static methods

A static method belongs to the type itself, not to any single instance, so you call it using the type name. Use TypeName.methodName() syntax for shared logic, and choose static when behaviour does not need per-instance state.

struct ScoreRules {
    static func bonus(for streak: Int) -> Int {
        return streak >= 3 ? 10 : 0
    }
}

let extra = ScoreRules.bonus(for: 4)

Static methods are good for helpers, validation, and factory-style setup logic where instance data is not required.

Mutating methods

Structs are value types, so methods that change stored properties must be marked with mutating. mutating tells Swift the method will change instance state, so assignments like score += 1 are allowed inside that method.

struct BankAccount {
    var owner: String
    var balance: Double

    mutating func deposit(_ amount: Double) {
        balance += amount
    }
}

Without mutating, Swift blocks property changes inside struct methods to protect value semantics and avoid accidental mutation.

Computed properties

Computed properties return a value calculated from other data, rather than storing that value directly in memory. Use them when a value should always stay derived from current state, and avoid duplicate stored data that can go out of sync.

struct Player {
    var name: String
    var score: Int

    var description: String {
        "\(name) has \(score) points."
    }
}

A computed property getter is closure-like because it is a block of code that runs each time the property is accessed. It can read self and build a result on demand, but it is not literally a closure value being passed around like { value in value * 2 }.

You can think of it as "method-like syntax with property-style access" which keeps call sites clean, for example print(player.description), skipping the () brackets.

Choosing between open functions and methods

Choose a method when behaviour clearly belongs to a model, and choose an open function when behaviour is generic and not owned by one type. Ask: "Does this action belong to this model?" if yes, prefer a method, ask: "Could this work for many unrelated types?" if yes, an open function may be better.

This decision improves readability and architecture, because related behaviour stays discoverable where developers expect to find it.

Task A: convert open functions to methods

  1. Create a struct called Book with properties title, author, and pages.
  2. Start by writing an open function bookSummary(title:author:pages:) -> String.
  3. Then move that behaviour into an instance method summary() -> String inside Book.
  4. Create two Book instances and print both summaries using the method version.
  5. Write one sentence explaining why the method version is clearer for this model.

Task B: static method challenge

  1. Create a struct called Temperature.
  2. Add two static methods:
  • toFahrenheit(celsius: Double) -> Double
  • toCelsius(fahrenheit: Double) -> Double
  1. Call both methods using type syntax (for example Temperature.toFahrenheit(celsius: 22)).
  2. Print clear output lines for at least three conversions.
  3. Explain why these are better as static methods than instance methods.

Task C: mutating method challenge

  1. Create a struct called Timer with properties seconds and isRunning.
  2. Add a mutating method start() that sets isRunning to true.
  3. Add a mutating method tick() that increases seconds by 1 only when isRunning is true.
  4. Add a mutating method reset() that sets seconds to 0 and isRunning to false.
  5. Test your struct by calling the methods in sequence and printing values after each step.

Task D: mixed design decision

  1. Build a struct Cart with property itemsCount starting at 0.
  2. Add a type-level rule with static let freeShippingThreshold = 5.
  3. Add a mutating method addItem() that increments itemsCount by 1.
  4. Add an instance method shippingMessage() -> String that uses itemsCount and returns:
  • "Free shipping" when itemsCount >= Cart.freeShippingThreshold
  • "Shipping applies" otherwise
  1. Add a static helper qualifiesForFreeShipping(count: Int) -> Bool so the shipping rule can be reused in other places (for example previews, reports, or tests without a Cart instance).
  2. Create one Cart instance and call addItem() in a loop.
  3. After each add, print both cart.itemsCount and cart.shippingMessage().
  4. In comments, explain why addItem() and shippingMessage() are instance behaviour, while freeShippingThreshold and qualifiesForFreeShipping are type-level behaviour.

Task E: method to computed property

  1. Create a struct called Badge with properties name and level.
  2. Add an instance method label() -> String that returns text like "Ranger - Level 4".
  3. Create at least one Badge instance and print the method result.
  4. Convert label() into a computed property label: String.
  5. Print the new computed property and confirm the output is the same.

Extension for Super Players!

Take one extra step to strengthen your design choices with computed properties. Add at least one computed property to each of Tasks A through D, or convert one existing method from any task into a computed property and explain why that change improves readability.

Summary

Methods help you keep behaviour close to the data it belongs to, while open functions remain useful for shared logic outside specific models. Instance methods work with one value's state, static methods provide type-level behaviour without needing an instance, and mutating methods allow safe state changes in structs when mutation is intentional.

Designing with these tools makes code easier to understand, test, and maintain especially as projects grow.

Review

Use these prompts to check your understanding. Explain the difference between an open function and a method in your own words, describe when a static method is the best choice, describe why structs need the mutating keyword for state changes, and write one example where you would prefer an open function over a method.