class — what it is, key concepts and how it works in Swift

Author: IT Sectr Published: 2026-06-17 Reading time: 8 min

Class is a reference type in the Swift language, whose instances are passed by reference rather than copied on assignment. Unlike structs, classes support inheritance, deinitialization, and automatic reference counting (ARC) for memory management. According to the Swift Programming Language Guide, 2026, class is required for working with Apple's UI frameworks (UIKit, AppKit) and implementing patterns that require shared object identity. The choice between class and struct is one of the key architectural decisions in Swift.

Key Takeaways

  • Class — a reference type in Swift where a variable stores a reference to an object in the heap, not the value itself.
  • Inheritance — classes can inherit properties and methods from a parent class and override them using override.
  • ARC — automatic reference counting frees memory when no strong references remain to an object.
  • Deinit — a special method called before an object is deallocated to clean up resources.
  • Identity — the === and !== operators check whether two variables reference the same class instance.

What is class in Swift?

Class — a composite reference type in Swift that groups properties and methods into a single entity with support for inheritance and dynamic dispatch. Unlike struct, a class instance is created in the heap, and a variable stores a reference to this instance, not the data itself.

When you assign a class variable to another variable, both reference the same object in memory. Changes through one reference are visible through another — this is a fundamental property of reference types, used in delegation, observation, and shared state patterns.

According to Apple Swift Documentation, classes are the only way to work with UIKit and AppKit, where all UI components inherit from UIView and UIViewController. Additionally, classes are necessary for implementing patterns that require object identity (two references to one object) and controlled lifetime.

Class as a reference type

Reference type semantics — the key property of classes. When assigning a class to a new variable, Swift copies the reference, not the data. All variables referencing the same instance see its current state and can modify it. This behavior differs fundamentally from value types, where each variable gets an independent copy.

swift
// Example of reference type semantics
class User {
    var name: String
    init(name: String) { self.name = name }
}

let user1 = User(name: "Alice")
let user2 = user1           // user2 — same reference
user2.name = "Bob"

print(user1.name)               // "Bob" — changed via user2!

// Identity check with === operator
print(user1 === user2)          // true — same object

The === (identity) and !== operators check whether two variables reference the same class instance. This differs from == (equality), which compares property values. The === operator is not available for struct — value types have no identity.

Class inheritance

Inheritance — a mechanism where a class can adopt the properties and methods of a parent class. In Swift, a class can only inherit from one parent (single inheritance) but can implement multiple protocols. The override keyword allows overriding an inherited method or property.

Base class and subclass

Any class that does not inherit from another class automatically becomes base (not to be confused with NSObject). A subclass specifies the parent with a colon after its name. If a subclass overrides a parent method, it must call super.method() to preserve the parent's behavior — this is a compiler requirement.

Final class

The keyword final before class prevents inheritance. The compiler can optimize method calls on a final class through static dispatch, improving performance. Use final for classes not intended for extension — it documents your intent and speeds up code.

swift
// Example of class inheritance in Swift
class Vehicle {
    var speed: Double = 0

    func description() -> String {
        "Speed: \(speed) km/h"
    }
}

// Car inherits Vehicle
class Car: Vehicle {
    var brand: String = "Unknown"

    override func description() -> String {
        "\(brand) - \(speed) km/h"
    }
}

// Final class — prevents inheritance
final class ElectricCar: Car {
    var batteryLevel: Double = 100
}

let tesla = ElectricCar()
tesla.brand = "Tesla"
tesla.speed = 120
print(tesla.description())  // "Tesla - 120.0 km/h"

Inheritance is a powerful but responsible mechanism. Deep hierarchies (5+ levels) complicate maintenance and testing. For functional reuse without inheritance, use protocols with extension and protocol-oriented programming — an approach Apple promotes as an alternative to deep class hierarchies.

Deinitialization and memory management

Swift uses ARC (Automatic Reference Counting) for class memory management. Each class instance has a strong reference counter. When a new strong reference is created, the counter increments; when destroyed, it decrements. When the counter reaches zero, memory is freed.

Deinit

Deinit — a method automatically called before a class instance is deallocated. It releases resources: closes files, unsubscribes from notifications, stops timers. Deinit is only available for classes — structs and enums do not have it.

Weak and unowned references

To prevent strong reference cycles (retain cycles), Swift provides weak and unowned references. Weak is an optional reference that automatically becomes nil when the object is deallocated. Unowned is non-optional but references an object that is guaranteed to outlive the current context. A typical retain cycle occurs in a parent-child relationship: child holds a strong reference to parent.

swift
// ARC and weak reference example
class Parent {
    var name: String
    var child: Child?

    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

class Child {
    var name: String
    weak var parent: Parent?   // weak prevents retain cycle

    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

var parent: Parent? = Parent(name: "Anna")
parent?.child = Child(name: "Mia")
parent?.child?.parent = parent

parent = nil  // Both objects deallocated!
// Without weak it would create a retain cycle

Always use weak for references from a child object to a parent and for capture lists in closures. Use unowned only when you are sure the object will outlive the current context — incorrect use of unowned can cause a crash when accessing deallocated memory.

Class vs Struct: key differences

The choice between class and struct is an architectural decision that affects performance, thread safety, and API design. Consider the table of key differences to help make the right decision in each case.

Characteristicclassstruct
TypeReference typeValue type (copy)
MemoryHeap + ARCStack / inlined
InheritanceSupports (single parent)Does not support
DeinitAvailableNot available
Identity (===)SupportsDoes not support
MutatingNot required (always mutating)Only with mutating
Memberwise initNot generatedGenerated automatically
Thread safetyNot guaranteed (shared state)Guaranteed (copying)

Use classes when you need shared identity (multiple parts of code working with one object), inheritance, or interaction with Objective-C runtime. For everything else, structs are preferred — they are faster, safer in multithreaded code, and require no memory management.

When to use class in Swift

Despite Apple's recommendation to use struct by default, classes are necessary in several specific scenarios. Let's examine each with practical examples.

UIKit and AppKit components

All UI components in iOS and macOS are classes inheriting from UIView (iOS) or NSView (macOS). You cannot replace UIViewController with a struct — it requires inheritance and deinit to release resources. When working with UIKit, use classes for controllers, views, and their delegates.

Singleton and shared state

The Singleton pattern (one class instance for the entire application) requires reference semantics. Managers: NetworkManager, SettingsManager, AnalyticsService — are typically implemented as classes with a shared static property. Structs are not suitable because each copy would be an independent instance.

Dependency Injection and reference objects

When an object must be the single source of truth and is passed between modules by reference — use class. This applies to state management, where changing an object in one place must be visible in all dependent components. For ObservableObject in SwiftUI, classes are required.

swift
// ObservableObject — class required for SwiftUI
import SwiftUI

class AppViewModel: ObservableObject {
    @Published var isLoggedIn: Bool = false
    @Published var username: String = ""

    func login(user: String) {
        isLoggedIn = true
        username = user
    }
}

// Usage in SwiftUI View
struct ContentView: View {
    @StateObject var viewModel = AppViewModel()

    var body: some View {
        Text(viewModel.isLoggedIn ? "Welcome" : "Login")
    }
}

Rule: if an object must have identity (two references -> one object), be a single instance, or work with UIKit/Objective-C — choose class. If an object simply contains data — choose struct.

Frequently Asked Questions

What is class in Swift?

Class is a reference type in Swift that supports inheritance, deinitialization, and ARC for memory management. Class instances are stored in the heap, and variables contain a reference to the object, not its copy.

How is class different from struct in Swift?

Class — reference type (passed by reference), supports inheritance and deinit. Struct — value type (copied), does not support inheritance, but implements protocols and gets memberwise init automatically. Swift recommends struct as the default type.

What is ARC in Swift?

ARC (Automatic Reference Counting) — a memory management mechanism for Swift classes. Each instance has a strong reference counter. When the counter reaches zero, memory is freed. Weak and unowned references prevent retain cycles between objects.

What is deinit in Swift?

Deinit — a class method automatically called before its memory is deallocated. Used for closing files, unsubscribing from notifications, and other cleanup operations. Deinit is only available in classes — structs do not have it.

When to use class in Swift?

Use class for UIKit/AppKit UI components, singleton, ObservableObject in SwiftUI, objects with shared identity (delegate, observer), and when working with Objective-C runtime. For data models, DTOs and configurations, struct is preferred.

Summary

  • Class is a reference type in Swift with pass-by-reference semantics: all variables pointing to one object see its current state and can modify it.
  • ARC manages class memory through strong reference counting; weak and unowned prevent retain cycles in parent-child and closures.
  • Inheritance allows classes to reuse and override parent behavior, but deep hierarchies (5+ levels) complicate code maintenance.
  • Deinit runs when an object is deallocated and is required for releasing resources — files, timers, NotificationCenter subscriptions.
  • UIKit and AppKit require classes for all UI components — UIView, UIViewController, their delegates and data sources.
  • ObservableObject in SwiftUI — class is required because SwiftUI tracks changes through @Published and @StateObject by reference.
  • Apple recommends struct as the default type for new data types — use class only when identity, inheritance, or Objective-C integration is needed.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also