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 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.
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.
// 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.
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.
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.
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.
// 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.
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 — 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.
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.
// 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.
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.
| Characteristic | class | struct |
|---|---|---|
| Type | Reference type | Value type (copy) |
| Memory | Heap + ARC | Stack / inlined |
| Inheritance | Supports (single parent) | Does not support |
| Deinit | Available | Not available |
| Identity (===) | Supports | Does not support |
| Mutating | Not required (always mutating) | Only with mutating |
| Memberwise init | Not generated | Generated automatically |
| Thread safety | Not 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.
Despite Apple's recommendation to use struct by default, classes are necessary in several specific scenarios. Let's examine each with practical examples.
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.
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.
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.
// 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
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.
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.
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.
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.
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
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.
Read also