Deinitialization

A deinitializer is called immediately before a class instance is freed. It is the mirror image of initialization: where init sets an object up, deinit gives it one last chance to clean up after itself. This is a short chapter because Swift handles most cleanup for you — but knowing exactly when deinit runs is essential for managing external resources correctly.

How deinitialization works

You write a deinitializer with the deinit keyword. Like an initializer it has no func keyword, and unlike an initializer it takes no parameters and no parentheses. Each class may have at most one deinitializer.

class FileHandle {
    let path: String
    init(path: String) {
        self.path = path
        print("Opened \(path)")
    }
    deinit {
        print("Closed \(path)")   // cleanup runs here
    }
}

A deinitializer can access any property of the instance and is typically used to release resources the object owns — close a file, sever a network connection, unsubscribe from a notification, or free a manually allocated buffer.

When the deinitializer is called

Swift calls deinit automatically the moment an instance is about to be deallocated — you never call it yourself. Deallocation happens when the last strong reference to the instance goes away, which Swift tracks with Automatic Reference Counting (ARC). Common moments include a variable going out of scope, being reassigned to something else, or being set to nil.

Superclass deinitializers run automatically: a subclass's deinit is called first, then its superclass's, all the way up the chain. You don't (and can't) call super.deinit() manually.

Watch out: Don't rely on deinit for timing-critical or guaranteed-prompt cleanup. It fires only when the instance is actually deallocated — and if a strong reference cycle keeps it alive, it may never fire at all. See Automatic Reference Counting for how to break those cycles with weak and unowned.

Deinitializers in action

Here's a small game where a Bank tracks a finite supply of coins and a Player returns its coins to the bank when it leaves the game. Setting the player to nil removes the only strong reference, so the deinitializer runs.

class Bank {
    static var coinsInBank = 10_000

    static func distribute(coins requested: Int) -> Int {
        let given = min(requested, coinsInBank)
        coinsInBank -= given
        return given
    }
    static func receive(coins: Int) {
        coinsInBank += coins
    }
}

class Player {
    var coinsInPurse: Int
    init(coins: Int) {
        coinsInPurse = Bank.distribute(coins: coins)
    }
    func win(coins: Int) {
        coinsInPurse += Bank.distribute(coins: coins)
    }
    deinit {
        Bank.receive(coins: coinsInPurse)   // hand coins back
    }
}
var player: Player? = Player(coins: 100)
print(Bank.coinsInBank)      // → 9900

player!.win(coins: 2_000)
print(player!.coinsInPurse)  // → 2100
print(Bank.coinsInBank)      // → 7900

player = nil                 // last reference gone → deinit runs
print(Bank.coinsInBank)      // → 10000 (coins returned)

Assigning nil deallocates the player, triggering deinit and returning its coins.

The relationship to ARC

Swift manages class instance memory through ARC. Every instance keeps a count of the strong references to it; when that count reaches zero, ARC deallocates the instance and calls its deinit first. You never free memory by hand — your job is simply to make sure references are released at the right time, which usually means avoiding strong reference cycles. Because ARC's bookkeeping is deterministic (not a garbage collector that runs later), deallocation — and thus deinit — happens precisely when the last reference drops.

Tip: A handy way to confirm an object is being freed as expected is to drop a print in its deinit during development. If the message never appears when you expect it to, you likely have a retain cycle leaking the instance.

Only classes have deinitializers

Deinitializers exist only on classes. Structures and enumerations are value types: they are copied rather than referenced, their lifetime isn't governed by ARC, and they have no concept of being the "last reference." Consequently they cannot define deinit. If a value type needs to perform cleanup tied to a resource, it typically wraps a class instance that owns the resource and lets that class's deinitializer do the work.

Wrapping up

A deinitializer is your one guaranteed hook just before a class instance disappears: write deinit, release whatever the object owns, and let ARC decide the timing by reference counting. Keep cleanup idempotent and avoid relying on it for prompt resource release. Next, we'll see how optional chaining lets you safely reach into objects that might be nil.