Lesson 65Classes

Class Initializers

Initializer Types

Classes have two types of initializers: designated (primary) and convenience (secondary). Understanding them is key to class inheritance!

Initializer Types

Designated

Primary init, must initialize ALL properties

init(params) { }

Convenience

Secondary init, calls designated init

convenience init() { }

Required

Subclasses MUST implement this init

required init() { }

Failable

Can return nil if init fails

init?() { }

Initializer Rules

  • Designated init must call super.init()
  • Convenience init must call self.init()
  • Convenience init must eventually call designated init
  • Initialize own properties BEFORE calling super.init()

Two-Phase Initialization

Phase 1: Initialize all stored properties (bottom-up)
Phase 2: Customize properties, can use self (top-down)
main.swift
// CLASS INITIALIZERS
// Classes have more complex initialization rules than structs

// DESIGNATED INITIALIZERS
// Primary initializer that fully initializes all properties
class Person {
    var name: String
    var age: Int

    // Designated initializer
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

// CONVENIENCE INITIALIZERS
// Secondary initializers that call designated initializers
class User {
    var username: String
    var email: String
    var age: Int

    // Designated initializer
    init(username: String, email: String, age: Int) {
        self.username = username
        self.email = email
        self.age = age
    }

    // Convenience initializer
    convenience init(username: String) {
        self.init(username: username, email: "\(username)@example.com", age: 0)
    }

    // Another convenience initializer
    convenience init(email: String) {
        let name = email.components(separatedBy: "@").first ?? "user"
        self.init(username: name, email: email, age: 0)
    }
}

let user1 = User(username: "john", email: "[email protected]", age: 25)
let user2 = User(username: "jane")  // Uses convenience init
let user3 = User(email: "[email protected]")

// INITIALIZER INHERITANCE
class Vehicle {
    var brand: String

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

    convenience init() {
        self.init(brand: "Unknown")
    }
}

class Car: Vehicle {
    var model: String

    init(brand: String, model: String) {
        self.model = model
        super.init(brand: brand)
    }

    // Override convenience init
    override convenience init(brand: String) {
        self.init(brand: brand, model: "Base Model")
    }
}

let car1 = Car(brand: "Tesla", model: "Model 3")
let car2 = Car(brand: "Toyota")  // Uses override
let car3 = Car()  // Inherits convenience init!

// REQUIRED INITIALIZERS
class Animal {
    var name: String

    required init(name: String) {
        self.name = name
    }
}

class Dog: Animal {
    var breed: String

    init(name: String, breed: String) {
        self.breed = breed
        super.init(name: name)
    }

    // MUST implement required init
    required init(name: String) {
        self.breed = "Mixed"
        super.init(name: name)
    }
}

// FAILABLE INITIALIZERS
class Product {
    var name: String
    var price: Double

    init?(name: String, price: Double) {
        guard !name.isEmpty else { return nil }
        guard price > 0 else { return nil }
        self.name = name
        self.price = price
    }
}

let validProduct = Product(name: "iPhone", price: 999)  // Optional(Product)
let invalidProduct = Product(name: "", price: 100)  // nil

// TWO-PHASE INITIALIZATION
class Player {
    var name: String
    var score: Int

    init(name: String, score: Int) {
        // Phase 1: Initialize all properties
        self.name = name
        self.score = score
        // Phase 2: Can now use self
        print("Player \(self.name) created!")
    }
}

Try It Yourself!

Create a BankAccount class with designated and convenience initializers. Add a failable init that rejects negative balances!