Okay so. We need to talk about classes.
If you've been following along with this series, you're probably feeling pretty comfortable with structs at this point. You've got properties, methods, initializers, computed properties, access control, the whole thing. You feel good. You feel ready.
And then Swift says: "Great. Now meet classes."
And you go: "...are those just structs with a different name?"
And Swift goes: "No."
And you go: "They look exactly like structs."
And Swift goes: "They are not exactly like structs. Sit down."
So. Let's sit down. 🍥
On the Surface, Classes Look Identical
Here's a class:
class AnimeCharacter {
var name: String
var powerLevel: Int
init(name: String, powerLevel: Int) {
self.name = name
self.powerLevel = powerLevel
}
func describe() {
print("\(name) — Power Level: \(powerLevel)")
}
}
And here's a struct doing the exact same thing:
struct AnimeCharacter {
var name: String
var powerLevel: Int
func describe() {
print("\(name) — Power Level: \(powerLevel)")
}
}
Spot the difference? One says class, one says struct. The struct doesn't need a custom init because Swift generates one automatically. Everything else looks basically the same.
So why do both exist? Why not just pick one and call it a day?
Because they behave completely differently in five important ways. And those five differences are not trivia — they're the reason you'll choose one over the other throughout your entire career as an iOS developer.
The Five Ways Classes Are Different From Structs
Let me walk through them one by one, because each one matters.
1. Classes Don't Get a Free Initializer
Remember how structs automatically generate a memberwise initializer? You define the properties and Swift quietly creates AnimeCharacter(name:powerLevel:) for you — no extra work required.
Classes don't get that.
With a class, you write the initializer yourself. Every time. Which at first sounds annoying, but there's a very good reason for it — and it connects to the next point.
2. Classes Can Inherit From Other Classes
This is the big one. The headline feature. The thing that makes classes genuinely different from structs rather than just structurally similar.
Inheritance means one class can be built on top of another class, automatically getting all its properties and methods as a starting point:
class Ninja {
var name: String
var village: String
init(name: String, village: String) {
self.name = name
self.village = village
}
func introduce() {
print("I am \(name) from \(village).")
}
}
class Hokage: Ninja {
var title: String
init(name: String, village: String, title: String) {
self.title = title
super.init(name: name, village: village)
}
func announce() {
print("I am \(title) \(name), leader of \(village)!")
}
}
Hokage inherits everything from Ninja — name, village, and introduce() — and adds its own stuff on top. You can create a Hokage and call introduce() on it even though Hokage never defined that method itself.
This is why classes don't get a free initializer. If they did, and you later added a property to Ninja, every Hokage initializer would silently break. Swift decided it's better to make you write your own, so you're always fully aware of what your initializer is doing and what it affects.
3. Copies of Classes Share the Same Data
This is the one that surprises people most, and it's genuinely important for understanding how SwiftUI works.
With a struct, every copy is independent:
var naruto = AnimeCharacterStruct(name: "Naruto", powerLevel: 9000)
var naruto2 = naruto
naruto2.powerLevel = 9001
print(naruto.powerLevel) // 9000 — unchanged
print(naruto2.powerLevel) // 9001 — only this one changed
With a class, copies point to the same underlying data:
var naruto = AnimeCharacterClass(name: "Naruto", powerLevel: 9000)
var naruto2 = naruto
naruto2.powerLevel = 9001
print(naruto.powerLevel) // 9001 — ALSO changed!
print(naruto2.powerLevel) // 9001
naruto and naruto2 aren't two separate characters — they're two names for the same character. Change one, you change both.
This sounds alarming, but it's actually exactly what you want for shared state in an app. If a user updates their profile name on one screen, you want every other screen showing that name to update automatically — not to be stuck showing the old version from their own private copy. Classes make that kind of shared, synchronized data easy.
4. Classes Have Deinitializers
When the very last reference to a class instance goes away, Swift can run a special function called a deinitializer — written as deinit:
class NinjaSchool {
var name: String
init(name: String) {
self.name = name
print("\(name) opened!")
}
deinit {
print("\(name) closed forever.")
}
}
Structs don't have this because each copy of a struct is independent — there's no "last copy" to track. Classes have it because when multiple things point to the same instance, Swift needs to know when truly nobody is using it anymore.
5. You Can Change Properties on a Constant Class
This one is subtle but important. With structs, a constant instance means nothing can change:
let naruto = AnimeCharacterStruct(name: "Naruto", powerLevel: 9000)
naruto.powerLevel = 9001 // ❌ can't change — it's a let
With classes, a constant instance just means you can't point it at a different instance — but the data inside can still change:
let naruto = AnimeCharacterClass(name: "Naruto", powerLevel: 9000)
naruto.powerLevel = 9001 // ✅ this is fine!
let naruto means "naruto always refers to this specific character." It doesn't mean "this character's properties can never change."
So When Do You Use a Class vs a Struct?
Most Swift developers default to structs. They're simpler, safer (because copies are independent), and Swift's standard library is built almost entirely on structs.
You reach for a class when you specifically want one of those five behaviors — most often the shared data one. In SwiftUI, your UI components are structs, but your data models that need to be shared across multiple views are classes. That's the pattern you'll see again and again as you build real apps.
The short version:
| Use a Struct when... | Use a Class when... |
|---|---|
| Each copy should be independent | Multiple things should share the same data |
| You want the free memberwise initializer | You need inheritance |
| You're building UI in SwiftUI | You're building data models in SwiftUI |
| Simple, contained data | Data that needs to be observed and shared |
The One Thing To Hold Onto
If everything above felt like a lot — and honestly, it is a lot for one sitting — the single most important thing to remember is this:
Structs give every copy its own data. Classes make all copies share the same data.
Everything else about classes (inheritance, deinit, constant mutability) flows from that fundamental difference. Once that clicks, the rest starts to make sense.
We'll go deeper on inheritance, deinitializers, and how all of this connects to SwiftUI in the next few articles. For now, just sit with the idea that structs and classes aren't interchangeable — they're different tools for different jobs, and knowing which one to reach for is one of the most important instincts you'll build as a Swift developer. 🌸
This article was written by me; AI was used to improve grammar and readability.
Top comments (1)
Nice article! I like how you focused on the practical differences instead of only explaining the theory. The examples were clear and easy to follow. 👏