# Swift Deinitializers: How deinit Works and When It Runs

Source: https://devdocs10.com/articles/swift/swift-deinitializers  
By Murat Bo · Published 2026-05-25 · DevDocs10 (https://devdocs10.com)

Swift deinitializers let a class instance clean up just before it is removed from memory. If you are learning classes and memory management, understanding `deinit` helps you write safer code, release resources at the right time, and diagnose bugs such as objects that never seem to go away.

> **Quick answer:** A Swift deinitializer is the `deinit` block inside a class. It runs automatically when the last strong reference to that class instance is removed, and it is mainly used to perform final cleanup such as stopping observers, closing resources, or logging object destruction.

> **Difficulty:** Beginner
>
> **Helpful to know first:** You'll understand this better if you know basic Swift class syntax, how properties work, and that Swift uses Automatic Reference Counting (ARC) to manage class instances.

## 1. What Is a Deinitializer?

A deinitializer is special code that belongs to a class and runs automatically when the instance is about to be removed from memory.

- It is written with the keyword `deinit`.
- Only classes can have deinitializers.
- You do not call a deinitializer yourself.
- It takes no parameters and has no return value.
- It is commonly used for final cleanup work.

In Swift, class instances are reference types. That means multiple variables can point to the same object. Swift uses ARC to keep track of how many strong references exist. When that count reaches zero, Swift destroys the instance and runs its deinitializer.

A deinitializer is closely related to an initializer, but they are not opposites in every detail. An initializer sets an object up when it is created. A deinitializer tears it down when it is no longer needed. Unlike `init`, a class can have only one `deinit`.

## 2. Why Deinitializers Matter

Many class instances do not need a custom deinitializer at all, because ARC automatically frees ordinary stored properties. However, some objects manage work or resources that should be stopped or released explicitly.

Deinitializers matter because they help you:

- remove observers before the object disappears
- invalidate timers or ongoing tasks
- close files or network-related helpers owned by the object
- release custom unmanaged resources
- debug object lifetime by printing when an instance is destroyed

Without proper cleanup, an object may leave behind extra work, unexpected behavior, or hidden memory problems. For example, if a class starts a timer and never invalidates it, the timer may continue running longer than expected.

> A deinitializer is for cleanup, not for normal business logic. If your program depends on `deinit` to perform everyday application work, the design is usually too fragile.

## 3. Basic Syntax

### Simple deinitializer syntax

The basic syntax is short. You place a `deinit` block inside a class definition.

```swift
class FileSession {
    let name: String

    init(name: String) {
        self.name = name
        print("Opened \(name)")
    }

    deinit {
        print("Closed \(name)")
    }
}
```

This class prints a message when it is created and another message when it is destroyed. The deinitializer has no parentheses because it cannot accept arguments.

### When it runs

Here is a minimal example showing object lifetime.

```swift
class UserSession {
    let username: String

    init(username: String) {
        self.username = username
        print("Session started for \(username)")
    }

    deinit {
        print("Session ended for \(username)")
    }
}

var session: UserSession? = UserSession(username: "Taylor")
session = nil
```

Output (tested with Swift 6.4):

```text
Session started for Taylor
Session ended for Taylor
```

When `session` is set to `nil`, the last strong reference is removed, so the object is deallocated and `deinit` runs.

### deinit vs init

The two are related but used differently.

```swift
class Counter {
    var value = 0

    init() {
        print("Counter created")
    }

    deinit {
        print("Counter destroyed")
    }
}
```

`init` prepares the object for use. `deinit` performs final cleanup after the object is no longer needed.

## 4. Step-by-Step Examples

### Example 1: Watching a simple object lifecycle

This first example shows the easiest way to see a deinitializer in action.

```swift
class Message {
    let text: String

    init(text: String) {
        self.text = text
        print("Created: \(text)")
    }

    deinit {
        print("Destroyed: \(text)")
    }
}

var message: Message? = Message(text: "Hello")
message = nil
```

Output (tested with Swift 6.4):

```text
Created: Hello
Destroyed: Hello
```

The output shows creation first and destruction second. This helps beginners understand that `deinit` is tied to the instance lifecycle.

### Example 2: Multiple references to the same object

A deinitializer does not run until the last strong reference is gone.

```swift
class Document {
    let title: String

    init(title: String) {
        self.title = title
    }

    deinit {
        print("Document removed: \(title)")
    }
}

var doc1: Document? = Document(title: "Report")
var doc2 = doc1

doc1 = nil
print("doc1 cleared")

doc2 = nil
print("doc2 cleared")
```

Output (tested with Swift 6.4):

```text
doc1 cleared
Document removed: Report
doc2 cleared
```

After `doc1 = nil`, the instance still exists because `doc2` still references it. Only after `doc2 = nil` does the deinitializer run.

### Example 3: Cleaning up helper work

This example simulates an object that starts work and must stop it before being destroyed.

```swift
class DownloadTask {
    var isRunning = false

    init() {
        isRunning = true
        print("Download started")
    }

    func stop() {
        if isRunning {
            isRunning = false
            print("Download stopped")
        }
    }

    deinit {
        stop()
        print("DownloadTask destroyed")
    }
}

var task: DownloadTask? = DownloadTask()
task = nil
```

Output (tested with Swift 6.4):

```text
Download started
Download stopped
DownloadTask destroyed
```

Here the deinitializer acts as a final safety net. If the task is still running when the object disappears, cleanup still happens.

### Example 4: Breaking a strong reference chain with weak

One common reason a deinitializer does not run is a retain cycle. This example shows the correct use of a weak reference.

```swift
class Owner {
    let name: String
    var device: Device?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("Owner destroyed: \(name)")
    }
}

class Device {
    let model: String
    weak var owner: Owner?

    init(model: String) {
        self.model = model
    }

    deinit {
        print("Device destroyed: \(model)")
    }
}

var owner: Owner? = Owner(name: "Chris")
var device: Device? = Device(model: "Tablet")

owner?.device = device
device?.owner = owner

owner = nil
device = nil
```

Output (tested with Swift 6.4):

```text
Owner destroyed: Chris
Device destroyed: Tablet
```

Because `owner` inside `Device` is weak, both objects can be released normally. If both references were strong, neither object would deinitialize.

## 5. Practical Use Cases

Deinitializers are most useful in classes that own something needing explicit shutdown or cleanup.

- Stopping a custom worker object when a screen controller or manager object goes away
- Removing notifications or observation tokens owned by a class instance
- Invalidating a timer that should not continue after the object is gone
- Closing a file handle wrapper or similar resource-owning helper
- Logging object destruction while debugging memory leaks
- Releasing unmanaged or low-level resources wrapped by a Swift class

If a class only stores ordinary Swift values and references that ARC can manage automatically, you may not need a deinitializer at all.

## 6. Common Mistakes

### Mistake 1: Expecting deinit to exist in a struct

Deinitializers belong to classes, not structures or enumerations. Beginners sometimes try to use `deinit` with a value type because they want cleanup behavior everywhere.

**Problem:** This code uses `deinit` in a struct, which Swift does not allow because structs are value types and are not managed by ARC in the same way as classes.

```swift
struct FileRecord {
    let name: String

    deinit {
        print("Cleaning up \(name)")
    }
}
```

**Fix:** Use a class if you need object lifetime behavior tied to ARC and `deinit`.

```swift
class FileRecord {
    let name: String

    init(name: String) {
        self.name = name
    }

    deinit {
        print("Cleaning up \(name)")
    }
}
```

The corrected version works because classes are reference types and support deinitializers.

### Mistake 2: Assuming deinit runs as soon as one variable becomes nil

If an object has multiple strong references, clearing only one of them is not enough. The instance stays alive until the last strong reference disappears.

**Problem:** This code assumes setting one variable to `nil` destroys the object immediately, but another strong reference still keeps it alive.

```swift
class Account {
    deinit {
        print("Account destroyed")
    }
}

var firstRef: Account? = Account()
var secondRef = firstRef

firstRef = nil
```

**Fix:** Remove every strong reference before expecting the object to deinitialize.

```swift
class Account {
    deinit {
        print("Account destroyed")
    }
}

var firstRef: Account? = Account()
var secondRef = firstRef

firstRef = nil
secondRef = nil
```

Output (tested with Swift 6.4):

```text
Account destroyed
```

The corrected version works because ARC can finally reduce the strong reference count to zero.

### Mistake 3: Creating a retain cycle so deinit never runs

Two class instances can keep each other alive if both store strong references. This is one of the most common reasons developers think `deinit` is "not working".

**Problem:** Both objects strongly reference each other, so ARC cannot reduce either one to zero references. Their deinitializers never run.

```swift
class Customer {
    var card: CreditCard?
    deinit {
        print("Customer destroyed")
    }
}

class CreditCard {
    var customer: Customer?
    deinit {
        print("CreditCard destroyed")
    }
}
```

**Fix:** Make one side `weak` or `unowned`, depending on the ownership relationship.

```swift
class Customer {
    var card: CreditCard?
    deinit {
        print("Customer destroyed")
    }
}

class CreditCard {
    weak var customer: Customer?
    deinit {
        print("CreditCard destroyed")
    }
}
```

The corrected version works because the weak reference does not increase the strong reference count.

### Mistake 4: Trying to call deinit manually

A deinitializer is controlled by ARC, not by your own code. You cannot invoke it like a normal method.

**Problem:** This code treats `deinit` like a function, but Swift does not allow manual calls to a deinitializer.

```swift
class Logger {
    deinit {
        print("Logger destroyed")
    }
}

let logger = Logger()
// logger.deinit()
```

**Fix:** Remove the strong references instead, or create a separate cleanup method if you need explicit manual shutdown before deallocation.

```swift
class Logger {
    func close() {
        print("Logger closed manually")
    }

    deinit {
        print("Logger destroyed")
    }
}

var logger: Logger? = Logger()
logger?.close()
logger = nil
```

Output (tested with Swift 6.4):

```text
Logger closed manually
Logger destroyed
```

The corrected version works because manual cleanup and automatic deallocation are handled separately.

## 7. Best Practices

### Use deinit for cleanup, not primary program flow

It is fine to release resources or stop work in a deinitializer, but your app should not depend on `deinit` to trigger essential user-facing behavior at a precise moment.

```swift
class Uploader {
    func finishUpload() {
        print("Upload finished")
    }

    deinit {
        print("Uploader destroyed")
    }
}
```

The preferred pattern is to use a normal method for explicit business actions and reserve `deinit` for final cleanup.

### Keep deinitializers short and predictable

A deinitializer should usually perform quick cleanup tasks. Very complex work inside `deinit` can make object lifetimes harder to reason about.

```swift
class ObserverBox {
    var isObserving = true

    func stopObserving() {
        if isObserving {
            isObserving = false
            print("Observation removed")
        }
    }

    deinit {
        stopObserving()
    }
}
```

This is easier to understand than packing a lot of unrelated logic directly into the deinitializer.

### Use weak or unowned references deliberately

If two objects reference each other, think carefully about ownership. One side often should not strongly own the other.

```swift
class Department {
    var manager: Manager?
}

class Manager {
    weak var department: Department?
}
```

This practice prevents retain cycles and allows deinitializers to run when the objects are no longer needed.

## 8. Limitations and Edge Cases

- Only classes can define `deinit`. Structs and enums cannot.
- A class can have only one deinitializer.
- You cannot call `deinit` directly.
- The exact moment of deinitialization depends on ARC and remaining strong references.
- If a retain cycle exists, `deinit` may never run until the cycle is broken.
- Stored properties are released automatically, so you do not need deinitializers for ordinary memory cleanup alone.
- If a class inherits from another class, the subclass deinitializer runs before the superclass is deinitialized as the instance is torn down.
- A common "deinit not working" situation is actually a hidden strong reference, such as a closure or another object still holding the instance.

> When debugging lifecycle issues, adding a simple `print` inside `deinit` is a practical way to see whether an object is actually being released.

## 9. Practical Mini Project

This mini project creates a small class that simulates a connection. It starts active, can be closed manually, and also guarantees cleanup in its deinitializer.

```swift
class Connection {
    let id: Int
    private var isOpen = false

    init(id: Int) {
        self.id = id
        isOpen = true
        print("Connection \(id) opened")
    }

    func send(message: String) {
        if isOpen {
            print("Sending: \(message)")
        } else {
            print("Cannot send, connection is closed")
        }
    }

    func close() {
        if isOpen {
            isOpen = false
            print("Connection \(id) closed manually")
        }
    }

    deinit {
        if isOpen {
            print("Connection \(id) closed in deinit")
        }
        print("Connection \(id) destroyed")
    }
}

var connection: Connection? = Connection(id: 101)
connection?.send(message: "Hello server")
connection?.close()
connection = nil
```

Output (tested with Swift 6.4):

```text
Connection 101 opened
Sending: Hello server
Connection 101 closed manually
Connection 101 destroyed
```

This example shows a clean design: explicit manual closing is available through `close()`, and `deinit` acts as a backup cleanup point when the object is finally released.

## 10. Key Points

- `deinit` is a class-only feature used for final cleanup before an instance is removed from memory.
- Swift runs a deinitializer automatically when the last strong reference to the instance is gone.
- You cannot call a deinitializer yourself, and it does not take parameters.
- Most ordinary memory cleanup is handled by ARC automatically.
- Deinitializers are especially useful for stopping work, removing observers, or releasing owned resources.
- If `deinit` does not run, a hidden strong reference or retain cycle is often the cause.
- `weak` and `unowned` references help prevent cycles that block deinitialization.

## 11. Practice Exercise

Try building a class that tracks a temporary editing session.

- Create a class named `EditingSession` with a `documentName` property.
- Print a message when the session starts.
- Add a method named `endSession()` that prints a manual closing message.
- Add a `deinit` that prints a destruction message.
- Create an optional instance, call `endSession()`, then set the variable to `nil`.

**Expected output:** You should see a start message, then a manual end message, then a deinitialization message.

**Hint:** Use a class, not a struct, and make the variable optional so you can set it to `nil`.

```swift
class EditingSession {
    let documentName: String

    init(documentName: String) {
        self.documentName = documentName
        print("Started editing \(documentName)")
    }

    func endSession() {
        print("Ended session for \(documentName)")
    }

    deinit {
        print("EditingSession destroyed for \(documentName)")
    }
}

var session: EditingSession? = EditingSession(documentName: "Notes.txt")
session?.endSession()
session = nil
```

Output (tested with Swift 6.4):

```text
Started editing Notes.txt
Ended session for Notes.txt
EditingSession destroyed for Notes.txt
```

## 12. Final Summary

Swift deinitializers are a small feature, but they teach an important part of how classes behave. A `deinit` block runs automatically when the last strong reference to a class instance disappears, which makes it the right place for final cleanup work. In everyday Swift code, that often means stopping background work, removing observers, invalidating timers, or logging object destruction while debugging.

The most important practical idea is that deinitializers depend on ARC. If an object still has a strong reference somewhere, it will not be destroyed yet. If a retain cycle exists, `deinit` may never run at all. Once you understand that relationship between `deinit`, strong references, and weak references, you can reason much more confidently about class lifetimes in Swift.

A good next step is to study Swift Automatic Reference Counting in more depth, especially strong, weak, and unowned references. That will make deinitializers feel much more predictable in real projects.
