Classes and structs look almost identical in Swift: both have properties, methods and initializers. The crucial difference is how they're shared. A struct is a value type: every variable holds its own copy. A class is a reference type: variables hold a reference to one shared object, so a change made through one variable is visible through all the others.
That shared identity is exactly what some things need. A bank account, a network connection, a game character or a data store is a single thing that several parts of an app may refer to, and they should all see the same, current state. Classes also support inheritance, so one class can build on another.
In this lesson you'll write classes and their initializers, see reference semantics in action, compare object identity with ===, create subclasses that override methods, prevent inheritance with final, and learn the simple rule Apple suggests for choosing between a class and a struct.
Declaring a class
class BankAccount {
let owner: String
private(set) var balance: Double
init(owner: String, balance: Double = 0) {
self.owner = owner
self.balance = balance
}
func deposit(_ amount: Double) {
balance += amount
}
}
let acc = BankAccount(owner: "Sam")
acc.deposit(50)- Classes don't get a memberwise initializer: you write
init, setting every property that has no default value. self.owner = ownerseparates the property from the parameter with the same name.- Methods can change properties without
mutating, and even aletreference allows that:letonly stops the variable pointing to a different object. private(set)lets anyone readbalancebut only the class change it.
Reference semantics and identity
let a = BankAccount(owner: "Ana")
let b = a // b refers to the same account
b.deposit(100)
print(a.balance) // 100.0: a sees the change
print(a === b) // true: the very same object=== checks whether two references point to the same object; !== checks they don't. == compares values and only works if the class adopts Equatable. Two different accounts with identical balances are still two different accounts, which is exactly the behaviour you want for things with identity.
Inheritance, override and super
A class can inherit from another, gaining its properties and methods:
class Animal {
let name: String
init(name: String) { self.name = name }
func sound() -> String { "..." }
func describe() -> String { "\(name) says \(sound())" }
}
class Dog: Animal {
override func sound() -> String { "Woof" }
}overrideis required to replace an inherited method, which prevents accidental overrides.super.method()calls the parent's version, so a subclass can extend rather than replace behaviour.- Mark a class
finalto stop it being subclassed, or a methodfinalto stop it being overridden. - A class inherits from only one parent, but can adopt many protocols.
Class or struct?
Apple's guidance is to use structs by default and choose a class when:
- You need shared identity: several parts of the app must see and change the same instance.
- You need inheritance, for example when subclassing a framework class like
UIViewController. - You need to run code when the object is destroyed, with
deinit.
Everything else, such as data models, settings and results, works well as structs.
Swift Classes Syntax
Class syntax
class Parent {
var x: Int
init(x: Int) { self.x = x }
func f() -> String { "parent" }
}
final class Child: Parent {
override func f() -> String { "child, " + super.f() }
}
let c = Child(x: 1)
let d = c // same object
c === d // trueSwift Classes Summary
- Classes are reference types: variables share one object, so changes are visible everywhere.
- Classes need an init that sets every property without a default.
- === compares identity; == needs Equatable.
- Subclasses use override to replace methods and super to call the parent's version; final prevents subclassing.
- Prefer structs; use classes for shared identity, inheritance or deinit.
Swift Classes Examples
Each example shows the code, the output it produces and what each part does.
Shared references
class Playlist {
var songs: [String] = []
func add(_ song: String) { songs.append(song) }
}
let mine = Playlist()
let shared = mine
shared.add("Heroes")
mine.add("Blue Monday")
print(mine.songs)
print(shared.songs.count)
print(mine === shared)
let other = Playlist()
print(mine === other)["Heroes", "Blue Monday"]
2
true
falseLine-by-line explanation
- Lines 6 to 7shared is another reference to the same playlist, not a copy.
- Lines 8 to 9Songs added through either reference go into the same object.
- Line 13=== confirms they're the same object.
- Lines 15 to 16A separately created playlist is a different object, even if it were equal in content.
Inheritance with override and super
class Account {
let owner: String
var balance: Double
init(owner: String, balance: Double) {
self.owner = owner
self.balance = balance
}
func monthEnd() {}
func summary() -> String { "\(owner): £\(balance)" }
}
class Savings: Account {
let rate: Double
init(owner: String, balance: Double, rate: Double) {
self.rate = rate
super.init(owner: owner, balance: balance)
}
override func monthEnd() { balance += balance * rate }
}
final class Current: Account {
override func monthEnd() {
super.monthEnd()
balance -= 2
}
}
let accounts: [Account] = [Savings(owner: "Sam", balance: 1000, rate: 0.01),
Current(owner: "Lea", balance: 500)]
for a in accounts {
a.monthEnd()
print(a.summary())
}Sam: £1010.0
Lea: £498.0Line-by-line explanation
- Lines 14 to 17A subclass sets its own properties first, then calls the parent's init with super.init.
- Line 18override replaces the parent's monthEnd for savings accounts.
- Lines 21 to 25final means nothing can subclass Current; it extends the parent's method with super.
- Lines 27 to 32An array of the parent type holds both kinds, and each runs its own monthEnd.
deinit and object lifetime
class Download {
let file: String
init(file: String) {
self.file = file
print("Started \(file)")
}
deinit {
print("Cleaned up \(file)")
}
}
var job: Download? = Download(file: "photo.jpg")
var alias = job
job = nil
print("job cleared")
alias = nil
print("alias cleared")Started photo.jpg
job cleared
Cleaned up photo.jpg
alias clearedLine-by-line explanation
- Lines 7 to 9deinit runs when the object is destroyed.
- Lines 12 to 13Two references point to the same object.
- Lines 14 to 16The object lives until the last reference is gone; the memory lesson explains how Swift counts references.
Common Swift Classes Mistakes
Expecting a class to be copied
let backup = settings // settings is a class
backup.darkMode = true // also changes settings!// make Settings a struct for copy behaviour, or create a new instance:
let backup = Settings(darkMode: settings.darkMode)Forgetting override
class Dog: Animal {
func sound() -> String { "Woof" } // error: overriding declaration requires an 'override' keyword
}class Dog: Animal {
override func sound() -> String { "Woof" }
}Leaving a property uninitialised
class User {
var name: String // error: class 'User' has no initializers
}class User {
var name: String
init(name: String) { self.name = name }
}Swift Classes in Real Projects
Classes in iOS apps
UIKit apps subclass framework classes such as UIViewController and UIView. SwiftUI apps use classes for shared, observable data: a model marked @Observable (or an ObservableObject) that several views read and update, such as a shopping cart or a user session, as the SwiftUI state lesson shows. Everything else is usually structs.
Swift Classes FAQ
When should I use a class instead of a struct in Swift?
Use a class when several parts of the app must share and change the same instance, when you need to subclass something such as UIViewController, or when you need deinit to run clean-up code. For plain data like models, settings and results, use a struct.
What is the difference between == and === in Swift?
== asks whether two values are equal according to the type's Equatable rules. === only works on class instances and asks whether two references point to exactly the same object. Two separate accounts with equal balances can be == but are never ===.
Why do Swift classes need an initializer?
Swift requires every stored property to have a value before an object can be used. Classes don't get a memberwise initializer, so unless every property has a default value, you must write an init that sets them all. The compiler reports any property you forgot.
What does final mean on a Swift class?
final class stops other classes inheriting from it, and a final method or property can't be overridden. It makes your intent clear and lets the compiler call methods more efficiently, because it knows no subclass can replace them.
Can a let constant holding a class still change?
Yes. let on a class reference only stops the constant pointing to a different object. The object's var properties can still change, so let account = BankAccount(owner: "Sam") followed by account.deposit(50) is allowed.
Swift Classes Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the subclass method so it replaces the parent's version.
class Cat: Animal {
func sound() -> String { "Meow" }
}
What kind of type is a class?
What does this print?
class Box { var n = 1 }
let a = Box()
let b = a
b.n = 7
print(a.n)
What does === check?
Why can you call a method that changes properties on a let class instance?
Swift Classes Quiz
5 questions to check what you learned.
Does a class get a memberwise initializer?
- Yes, like structs
- Only final classes
- Only subclasses
- No, you write init yourself
What does
final classprevent?- Other classes inheriting from it
- Creating instances
- Changing properties
- Using protocols
What does
super.init(...)do in a subclass's initializer?- Creates a second object
- Deletes the parent
- Calls the parent class's initializer to set up the inherited properties
- Copies the object
When does
deinitrun?- When the object is created
- When the last reference to the object goes away and it's destroyed
- Every time a method is called
- Only in structs
Which is the best fit for a class rather than a struct?
- A shared shopping cart that several screens read and update
- A Point with x and y
- A product loaded from JSON
- A colour value