Skip to main content

Working with Classes

1. Defining Classes

Feature Description Example
Class Declaration Defines a new class using the class keyword.
class MyClass {
    // Class body
}
Inheritance Classes can inherit from a single parent class using :.
class Vehicle {
    var speed: Int = 0
}

class Car: Vehicle {
    var doors: Int = 4
}
Properties Variables and constants stored in a class.
class Person {
    var name: String
    let id: Int
    
    init(name: String, id: Int) {
        self.name = name
        self.id = id
    }
}
Methods Functions defined within a class.
class Calculator {
    func add(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
}
Initializers Code that runs when an instance is created.
class Rectangle {
    var width: Double
    var height: Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
}
Deinitializers Code that runs when an instance is deallocated.
class FileHandler {
    deinit {
        // Clean up resources
    }
}

2. Creating Class Instances

Method Description Example
init() Default initializer for a class
class Person {
    var name: String = ""
    
    init() {
        self.name = "Unknown"
    }
}

let person = Person()
init(parameters) Custom initializer with parameters
class Car {
    var make: String
    var model: String
    
    init(make: String, model: String) {
        self.make = make
        self.model = model
    }
}

let myCar = Car(make: "Toyota", model: "Camry")
convenience init() Convenience initializer that calls another initializer
class Rectangle {
    var width: Double
    var height: Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
    
    convenience init(side: Double) {
        self.init(width: side, height: side)
    }
}

let square = Rectangle(side: 5.0)

Example: Creating Instances with Different Initializers

class Student {
    var name: String
    var gradeLevel: Int
    
    init(name: String, gradeLevel: Int) {
        self.name = name
        self.gradeLevel = gradeLevel
    }
    
    convenience init(name: String) {
        self.init(name: name, gradeLevel: 1)
    }
}

let student1 = Student(name: "Alice", gradeLevel: 10)
let student2 = Student(name: "Bob")

3. Understanding Reference Semantics

Concept Description Notes
Reference Type Classes are reference types in Swift. Multiple variables can point to the same instance.
Value Type Structs and enums are value types in Swift. Each variable has its own copy of the data.
Memory Allocation Reference types are allocated on the heap. Value types are typically allocated on the stack.
Identity vs Equality Reference types can be checked for identity using ===. Value types are compared by value, not identity.

Example: Reference Type Behavior

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

var person1 = Person(name: "Alice")
var person2 = person1

person2.name = "Bob"

print(person1.name) // Output: Bob
print(person1 === person2) // Output: true

Example: Value Type Behavior

struct Person {
    var name: String
}

var person1 = Person(name: "Alice")
var person2 = person1

person2.name = "Bob"

print(person1.name) // Output: Alice
print(person1 == person2) // Output: true
Note: Understanding reference semantics is crucial for managing memory and avoiding unexpected behavior in Swift applications.

4. Using Inheritance

Feature Description Example
class inheritance Defines a new class that inherits from a superclass
class Vehicle {
    var speed: Int = 0
}

class Car: Vehicle {
    var doors: Int = 4
}
overriding methods Replaces a superclass method with a new implementation
class Animal {
    func makeSound() {
        print("Some sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Woof!")
    }
}
super keyword Accesses the superclass's implementation of a method or property
class Vehicle {
    var speed: Int = 0
}

class Car: Vehicle {
    override var speed: Int {
        get {
            return super.speed
        }
        set {
            super.speed = newValue
        }
    }
}

Example: Inheritance Hierarchy

class Shape {
    var color: String = "blue"
    
    func area() -> Double {
        return 0.0
    }
}

class Rectangle: Shape {
    var width: Double = 0.0
    var height: Double = 0.0
    
    override func area() -> Double {
        return width * height
    }
}

class Square: Rectangle {
    var side: Double = 0.0
    
    override var width: Double {
        get {
            return side
        }
        set {
            side = newValue
        }
    }
    
    override var height: Double {
        get {
            return side
        }
        set {
            side = newValue
        }
    }
}
Note: Swift supports single inheritance only. A class can inherit from one superclass, but can conform to multiple protocols.

5. Overriding Methods

Method Description Notes
override Keyword to indicate method overriding Must be used when overriding a superclass method

Example: Overriding a Method

class Animal {
    func makeSound() {
        print("Animal makes a sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Dog barks")
    }
}

let dog = Dog()
dog.makeSound() // Output: "Dog barks"
Note: The override keyword is required when overriding a method from a superclass.

6. Using Super to Access Parent

Feature Description Usage
super Keyword to access parent class methods and properties super.methodName() or super.propertyName

Example: Calling Parent Method

class Vehicle {
    func startEngine() {
        print("Vehicle engine started")
    }
}

class Car: Vehicle {
    override func startEngine() {
        super.startEngine()
        print("Car engine started")
    }
}

let myCar = Car()
myCar.startEngine()
// Output:
// Vehicle engine started
// Car engine started

Example: Accessing Parent Property

class Animal {
    var name: String = "Generic Animal"
}

class Dog: Animal {
    override var name: String {
        get {
            return super.name + " (Dog)"
        }
        set {
            super.name = newValue
        }
    }
}

let myDog = Dog()
print(myDog.name)
// Output: Generic Animal (Dog)

7. Preventing Overrides

Feature Description Usage
final Prevents a class from being subclassed final class MyClass { }
final (method) Prevents a method from being overridden final func myMethod() { }

Example: Using final to prevent subclassing

final class BaseClass {
    func doSomething() {
        print("Base implementation")
    }
}

// This will cause a compile-time error
// class DerivedClass: BaseClass { } // ❌ Error: 'BaseClass' is final

Example: Using final to prevent method override

class Parent {
    final func protectedMethod() {
        print("This cannot be overridden")
    }
}

class Child: Parent {
    // This will cause a compile-time error
    // override func protectedMethod() { } // ❌ Error: cannot override 'protectedMethod'
}

8. Using Type Properties and Methods

Property/Method Description Example
type property Shared by all instances of a type, declared with static or class
struct SomeStruct {
    static var shared = SomeStruct()
}
type method Callable on the type itself, not instances; declared with static or class
class SomeClass {
    static func createInstance() -> SomeClass {
        return SomeClass()
    }
}

Example: Type Properties and Methods in a Class

class Vehicle {
    static var totalVehicles = 0
    let model: String
    
    init(model: String) {
        self.model = model
        Vehicle.totalVehicles += 1
    }
    
    static func getVehicleCount() -> Int {
        return totalVehicles
    }
}

let car1 = Vehicle(model: "Tesla Model S")
let car2 = Vehicle(model: "Ford Mustang")

print(Vehicle.getVehicleCount()) // 2
Note: static properties and methods are not overridden in subclasses, whereas class ones can be.

9. Implementing Deinitializers

Deinitializers are used to perform cleanup tasks before an instance of a class is deallocated. They are defined using the deinit keyword and are called automatically when an instance is about to be deallocated.

Feature Description Notes
deinit deinit keyword Used to define a deinitializer
Execution Called before deallocation Automatically invoked by the runtime
Limitation Cannot be called explicitly No parameters allowed

Example: Basic Deinitializer

class MyClass {
    var name: String
    
    init(name: String) {
        self.name = name
        print("Initializing \(name)")
    }
    
    deinit {
        print("Deinitializing \(name)")
    }
}

let obj = MyClass(name: "Test")
// Output:
// Initializing Test

// When obj goes out of scope, deinit is called
// Output:
// Deinitializing Test
Note: Deinitializers are not called if the instance is still referenced by a strong reference.
Warning: Avoid retaining strong references in deinitializers to prevent retain cycles.

10. Understanding Identity Operators (===, !==)

Operator Description Usage Notes
=== Identity operator checks if two constants or variables refer to the same instance if object1 === object2 { ... } Unlike ==, which compares values, === compares object identity
!== Identity operator checks if two constants or variables refer to different instances if object1 !== object2 { ... } Useful for checking if two references point to different objects

Example: Using Identity Operators

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

let person1 = Person(name: "Alice")
let person2 = Person(name: "Alice")
let person3 = person1

print(person1 === person2) // false - different instances
print(person1 === person3) // true - same instance
Note: === and !== are only available for class instances, not value types like structs or enums.

Was this article helpful?