Lesson 69Classes

Class vs Struct

The Big Picture

Choosing between class and struct is one of the most important decisions in Swift. Understanding their differences helps you write better, more efficient code!

Key Differences

FeatureStructClass
TypeValue TypeReference Type
InheritanceNoYes
DeinitializerNoYes
Identity (===)NoYes
Memberwise InitAutomaticManual
let MutabilityFully immutableProperties mutable

When to Use

Use Struct When:

  • Simple data containers
  • No inheritance needed
  • Value semantics preferred
  • Thread safety matters
  • Copying is logical

Use Class When:

  • Inheritance required
  • Identity matters (===)
  • Shared mutable state
  • Deinitializer needed
  • Objective-C interop

Apple's Recommendation

"Start with structs by default!"

Use classes only when you specifically need reference semantics or class-only features like inheritance.

Copy-on-Write

Swift's standard library collections (Array, Dictionary, Set) use Copy-on-Write - they only copy data when you actually modify the copy, making them efficient despite being value types!

main.swift
// CLASS VS STRUCT
// Understanding when to use each

// STRUCT - Value Type
struct PointStruct {
    var x: Double
    var y: Double
}

var point1 = PointStruct(x: 10, y: 20)
var point2 = point1  // COPY is made

point2.x = 100

print(point1.x)  // 10 (unchanged!)
print(point2.x)  // 100

// CLASS - Reference Type
class PointClass {
    var x: Double
    var y: Double

    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
}

var pointA = PointClass(x: 10, y: 20)
var pointB = pointA  // Same reference

pointB.x = 100

print(pointA.x)  // 100 (changed!)
print(pointB.x)  // 100

// ===== KEY DIFFERENCES =====

// 1. INHERITANCE
// Classes support inheritance
class Animal {
    func speak() { print("...") }
}

class Dog: Animal {
    override func speak() { print("Woof!") }
}

// Structs DON'T support inheritance
struct Cat {
    func speak() { print("Meow!") }
}
// struct Kitten: Cat { }  // ERROR!

// 2. IDENTITY CHECKING
let classA = PointClass(x: 0, y: 0)
let classB = classA
let classC = PointClass(x: 0, y: 0)

print(classA === classB)  // true (same instance)
print(classA === classC)  // false (different instances)

// Structs don't have identity
// point1 === point2  // ERROR! === only for classes

// 3. MUTABILITY
let constantStruct = PointStruct(x: 1, y: 2)
// constantStruct.x = 10  // ERROR! Can't mutate let struct

let constantClass = PointClass(x: 1, y: 2)
constantClass.x = 10  // OK! Reference is constant, not the data

// 4. DEINITIALIZERS
class Resource {
    deinit {
        print("Cleanup!")  // Only classes have deinit
    }
}

// 5. COPY BEHAVIOR IN FUNCTIONS
func modifyStruct(_ point: PointStruct) {
    var localPoint = point
    localPoint.x = 999  // Only changes local copy
}

func modifyClass(_ point: PointClass) {
    point.x = 999  // Changes original!
}

var structPoint = PointStruct(x: 0, y: 0)
modifyStruct(structPoint)
print(structPoint.x)  // 0 (unchanged)

var classPoint = PointClass(x: 0, y: 0)
modifyClass(classPoint)
print(classPoint.x)  // 999 (changed!)

// ===== WHEN TO USE STRUCT =====
// - Simple data containers
// - No need for inheritance
// - Value semantics make sense
// - Thread-safe by default

struct User {
    var name: String
    var email: String
    var age: Int
}

struct Rectangle {
    var width: Double
    var height: Double

    var area: Double {
        return width * height
    }
}

// ===== WHEN TO USE CLASS =====
// - Need inheritance
// - Need identity (===)
// - Need deinitializers
// - Shared mutable state
// - Interop with Objective-C

class ViewController {
    var title: String
    var isLoading: Bool = false

    init(title: String) {
        self.title = title
    }

    func loadData() {
        isLoading = true
        // ... load data
        isLoading = false
    }
}

class NetworkManager {
    static let shared = NetworkManager()  // Singleton

    private init() { }

    func fetch(url: String) {
        print("Fetching: \(url)")
    }
}

// ===== PERFORMANCE CONSIDERATIONS =====
// Structs: Stack allocated (usually faster)
// Classes: Heap allocated + reference counting

// BUT: Large structs might be slower to copy
// Swift uses Copy-on-Write for standard library types

var array1 = [1, 2, 3, 4, 5]
var array2 = array1  // No copy yet!
array2.append(6)     // Copy happens here

Try It Yourself!

Think about a real-world entity (like a BankAccount). Would you model it as a struct or class? What are the trade-offs? Implement both and observe the behavioral differences!