Swift OOP

Swift's classes are reference types while structs are value types, and understanding that distinction is the foundation of everything else in Swift's object model.

Classes and Structs

Swift gives you two primary building blocks for modeling data: classes and structs. Both can declare properties, methods, and initializers, and both can conform to protocols, but they behave very differently the moment you assign an instance to a new variable or pass it into a function.

Value Types vs Reference Types

A struct is a value type: assigning it to a new variable, or passing it as a function argument, copies its entire contents. A class is a reference type: assigning an instance to a new variable creates a second reference to the exact same object in memory, so mutating through one variable is visible through the other.

  • Structs are copied on assignment and on function calls; classes are shared by reference.
  • Structs receive a free memberwise initializer; classes must define their own init or rely on default values.
  • Structs cannot inherit from another struct; classes support single inheritance from one superclass.
  • Copies of a struct are independent, which makes structs safer to share across threads.
  • Classes support identity comparison with the === and !== operators; structs have no notion of identity.

Struct value semantics

struct Point {
    var x: Int
    var y: Int
}

var a = Point(x: 1, y: 2)
var b = a          // b is a full copy of a
b.x = 99

print("a.x = \(a.x)")   // a.x = 1 (unchanged)
print("b.x = \(b.x)")   // b.x = 99

Class reference semantics

class Counter {
    var value = 0
}

let c1 = Counter()
let c2 = c1          // c2 points to the same instance as c1
c2.value = 42

print("c1.value = \(c1.value)")   // 42, both refer to the same object
print(c1 === c2)                  // true: same identity
Note: Prefer structs by default in Swift. Reach for a class only when you specifically need reference semantics, class inheritance, deinitializers, or interoperability with an Objective-C/Cocoa API that expects a class instance.

Initializers, Computed Properties, and Methods

Both classes and structs can define custom initializers, computed properties that calculate a value on access, and methods that operate on their data. The one difference: a struct method that changes any property must be marked mutating, because struct instance methods otherwise treat self as read-only.

A class with init, computed property, and methods

import Foundation

class BankAccount {
    let owner: String
    private var balance: Double

    init(owner: String, openingBalance: Double = 0) {
        self.owner = owner
        self.balance = openingBalance
    }

    var formattedBalance: String {
        String(format: "$%.2f", balance)
    }

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

    func withdraw(_ amount: Double) throws {
        guard amount <= balance else {
            throw NSError(domain: "BankAccount", code: 1)
        }
        balance -= amount
    }
}

let account = BankAccount(owner: "Priya", openingBalance: 100)
account.deposit(50)
print(account.formattedBalance)   // $150.00
AspectStructClass
KindValue typeReference type
Assignment behaviorCopies the valueShares the reference
InheritanceNot supportedSingle inheritance
Free initializerMemberwise init generated automaticallyNone; you must write your own
Mutating methodsRequire the mutating keyword to change selfNot needed; methods can freely change properties
Identity operator (===)Not applicableSupported
Note: Because structs are value types, a struct stored in a let constant cannot have its properties changed afterward, even if those properties were declared with var. Store it in a var if you need to mutate it later.

Exercise: Swift OOP

What is a core difference between a class and a struct in Swift?