DEV Community

Gamya
Gamya

Posted on

Swift Classes โ€” Deinitializers and How Swift Cleans Up After Itself ๐Ÿงน

Every class instance is eventually destroyed. Swift gives you a way to know exactly when that happens โ€” and the story of how it decides when to pull the trigger is surprisingly interesting.

Here's something you've probably never thought about: what happens to your objects when you're done with them?

With structs, the answer is simple. A struct lives as long as whatever owns it. Create a struct inside a function, the function ends, the struct is gone. Clean, predictable, boring in the best way.

Classes are messier. Remember โ€” when you "copy" a class, you're not copying the data, you're copying a signpost pointing to the data. That means multiple parts of your code might be holding onto a reference to the same class instance. So when does that instance actually get destroyed? When the first reference goes away? The last?

The answer is: the last one. And Swift has a whole system behind the scenes to track it. ๐Ÿฅ


Automatic Reference Counting โ€” The Invisible Accountant

Behind every class instance in Swift, there's a hidden counter. Every time you create a new reference to a class instance โ€” by assigning it to a variable, passing it to a function, storing it in an array โ€” Swift adds 1 to that counter. Every time one of those references goes away, Swift subtracts 1.

When the counter hits zero, Swift knows: nobody is looking at this anymore. It's safe to destroy it.

This system is called Automatic Reference Counting, or ARC. You never have to manage it yourself โ€” Swift handles it invisibly, continuously, for every class instance in your app.

Structs don't need ARC because each copy of a struct is its own independent piece of data. When a struct variable goes away, the data goes with it. Simple. No counting needed.

Classes need ARC precisely because the signpost system means multiple variables can point at the same data. Without counting references, Swift would have no way to know when the last one was gone.


Enter the Deinitializer

When Swift's reference counter hits zero and a class instance is about to be destroyed, Swift will call a special method called the deinitializer โ€” written as deinit.

It's the counterpart to init. Where init runs when an instance is created, deinit runs when the last reference to it disappears.

Let's see it in action with an anime training camp:

class TrainingCamp {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) has opened! ๐Ÿ•๏ธ")
    }

    deinit {
        print("\(name) has been abandoned. Training is over.")
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let's create some camps and watch them get destroyed:

var camp1: TrainingCamp? = TrainingCamp(name: "Konoha Training Grounds")
var camp2: TrainingCamp? = TrainingCamp(name: "Sand Village Camp")
var camp3: TrainingCamp? = camp1 // camp1 and camp3 now point to the same camp

camp1 = nil // reference count for Konoha drops to 1 (camp3 still holds it)
camp2 = nil // reference count for Sand Village drops to 0 โ€” deinit fires!
camp3 = nil // reference count for Konoha drops to 0 โ€” deinit fires!
Enter fullscreen mode Exit fullscreen mode

Output:

Konoha Training Grounds has opened! ๐Ÿ•๏ธ
Sand Village Camp has opened! ๐Ÿ•๏ธ
Sand Village Camp has been abandoned. Training is over.
Konoha Training Grounds has been abandoned. Training is over.
Enter fullscreen mode Exit fullscreen mode

Notice: camp2 fires its deinitializer as soon as camp2 = nil because that was the only reference. Konoha's deinitializer doesn't fire until camp3 = nil, because even after camp1 was set to nil, camp3 was still keeping the count at 1.


A Few Things Worth Knowing About deinit

You can't call it yourself. deinit is called by Swift automatically when the reference count hits zero. You can define it, but you can't trigger it manually.

It takes no parameters. Unlike init, deinit doesn't accept any arguments. It runs with no input โ€” it just gets told "you're being destroyed, do whatever you need to do."

Where to put it? Technically anywhere in the class. But there's a lovely convention: put it at the end of the class body. Code should read like a chapter in a book, and deinit is the final scene โ€” the fin.


The Other Thing About Variables in Classes

While we're on the topic of classes behaving differently from structs, there's one more quirk worth naming.

Remember from the structs chapter: if you create a let constant struct, nothing about it can change โ€” not its properties, not anything.

Classes don't work that way. A let class means "this variable will always point to the same instance." It does not mean "the instance can't change."

class Ninja {
    var powerLevel: Int

    init(powerLevel: Int) {
        self.powerLevel = powerLevel
    }
}

let naruto = Ninja(powerLevel: 9000)
naruto.powerLevel = 9001 // โœ… This is fine!
naruto = Ninja(powerLevel: 1) // โŒ This isn't โ€” you can't reassign the signpost
Enter fullscreen mode Exit fullscreen mode

let naruto means naruto's signpost is locked โ€” it will always point to the same Ninja instance. But the data at the end of that signpost? Completely free to change.

This makes sense once you think about the signpost analogy. let locks the signpost in place. It says nothing about what the signpost points to.


Why This Matters in Real iOS Development

ARC and deinitializers matter most when:

  • You're managing resources that need explicit cleanup โ€” network connections, file handles, observers, timers
  • You need to debug memory issues and want to know exactly when objects are being created and destroyed
  • You're working with complex object graphs where multiple things hold references to the same data

For everyday SwiftUI development you won't write deinit constantly. But understanding that it exists โ€” and understanding why it exists (because ARC needs a hook for cleanup) โ€” helps you understand the memory model of your app rather than just hoping things work out.


The One Thing To Hold Onto

Swift counts how many things are pointing at each class instance. When the count hits zero, it calls deinit and destroys the object. That's ARC in a nutshell.

And let on a class means the signpost is constant โ€” not the data it points to. That's the quirk that trips people up until the signpost analogy finally clicks. ๐ŸŒธ


This article was written by me; AI was used to improve grammar and readability.


Top comments (1)

Collapse
 
merbayerp profile image
Mustafa ERBAY

Nice explanation, especially the โ€œsignpostโ€ analogyโ€”it makes ARC much easier to visualize.

One thing Iโ€™d add is that deinit is not guaranteed to run simply because an object goes out of scope. If strong reference cycles exist, the reference count never reaches zero, so deinit wonโ€™t be called. Since retain cycles are one of the most common memory issues in Swift, even a brief mention of weak and unowned references would make this chapter even more complete.

Reference: Swift Programming Language โ€“ Automatic Reference Counting
docs.swift.org/swift-book/document...