DEV Community

Gamya
Gamya

Posted on

Swift Classes — Everything You Knew About Structs Just Got More Complicated

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)")
    }
}
Enter fullscreen mode Exit fullscreen mode

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)")
    }
}
Enter fullscreen mode Exit fullscreen mode

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)!")
    }
}
Enter fullscreen mode Exit fullscreen mode

Hokage inherits everything from Ninjaname, 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.")
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
the17yodev profile image
Young Dev

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. 👏