# Swift ARC (Automatic Reference Counting): Memory Management Explained

Source: https://devdocs10.com/articles/swift/swift-arc  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift uses Automatic Reference Counting, or ARC, to manage the lifetime of class instances for you. Understanding ARC helps you write code that creates objects when needed, releases them when they are no longer used, and avoids memory leaks caused by reference cycles.

> **Quick answer:** ARC tracks how many strong references point to a class instance. When the count reaches zero, Swift deinitializes the instance automatically. You control ARC by choosing between strong, weak, and unowned references.

> **Difficulty:** Beginner
>
> **You'll understand this better if you know:** basic Swift variables, classes, and the difference between value types and reference types.

## 1. What Is ARC?

Automatic Reference Counting is Swift's system for tracking how many active strong references exist for each class instance. A reference is a variable, constant, property, or collection element that points to an object.

- ARC applies to `class` instances, not to structs or enums.
- Each strong reference increases the instance's reference count by one.
- When a strong reference goes away, the count decreases.
- When the count reaches zero, Swift deallocates the instance.

ARC is automatic, but it is not magic. You still decide which references should be strong, weak, or unowned so your object graph can be released correctly.

## 2. Why ARC Matters

ARC gives Swift memory management with predictable performance and without requiring you to manually free objects. That is especially important in apps with many view models, controllers, models, and long-lived services.

It matters because memory bugs often come from objects keeping each other alive too long. ARC helps prevent use-after-free problems by tying an object's lifetime to its references, but you must still avoid reference cycles.

## 3. Basic Syntax

### Strong references keep objects alive

By default, assignments create strong references. As long as at least one strong reference exists, the instance stays in memory.

```swift
class Person {
    let name: String

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

    deinit {
        print("\(name) was deinitialized")
    }
}

var person: Person? = Person(name: "Ava")
person = nil
```

Output (tested with Swift 6.4):

```text
Ava was deinitialized
```

This example creates a `Person`, keeps it alive with a strong reference, and then releases it by setting the variable to `nil`.

### ARC uses reference counts behind the scenes

You do not call retain or release directly in Swift. ARC increments and decrements counts automatically as references are created, reassigned, or go out of scope.

A useful mental model is: more strong references means the object stays alive longer; fewer strong references means the object can be destroyed sooner.

## 4. Step-by-Step Examples

### Example 1: One strong reference

This is the simplest ARC flow. The instance exists while the variable holds it and disappears when that variable is cleared.

```swift
class Car {
    let model: String

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

    deinit {
        print("Car released")
    }
}

var car: Car? = Car(model: "Sedan")
car = nil
```

Output (tested with Swift 6.4):

```text
Car released
```

Once `car` becomes `nil`, ARC can release the instance because no strong references remain.

### Example 2: Multiple strong references

Two variables can point to the same instance. The instance stays alive until both references are removed.

```swift
class Book {
    let title: String

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

    deinit {
        print("Book released")
    }
}

var first: Book? = Book(title: "Swift Basics")
var second = first

first = nil
second = nil
```

Output (tested with Swift 6.4):

```text
Book released
```

Setting `first` to nil is not enough because `second` still holds the object. Only after the last strong reference disappears does ARC release it.

### Example 3: A retain cycle

Two objects that strongly reference each other can keep each other alive forever. This is one of the most important ARC problems to understand.

```swift
class Customer {
    var card: CreditCard?
    let name: String

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

    deinit {
        print("Customer released")
    }
}

class CreditCard {
    let number: String
    let customer: Customer

    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }

    deinit {
        print("Credit card released")
    }
}
```

This version leaks because `Customer` owns `CreditCard` and `CreditCard` owns `Customer`. Neither reference count can reach zero.

### Example 4: Fixing a cycle with weak references

Use `weak` when a reference should not keep the object alive. This is common for back-references such as delegates and parent pointers.

```swift
class Customer {
    var card: CreditCard?
    let name: String

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

    deinit {
        print("Customer released")
    }
}

class CreditCard {
    let number: String
    weak var customer: Customer?

    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }

    deinit {
        print("Credit card released")
    }
}
```

Now the card does not keep the customer alive. When the last strong reference to the customer disappears, both objects can be released normally.

## 5. Practical Use Cases

- Model objects that own other model objects through strong references.
- Delegation patterns where the delegate should be weak to avoid cycles.
- Parent-child relationships in trees, where children often hold a weak reference to the parent.
- Closures that capture `self` and need an explicit capture list to avoid retaining the owner.
- View models, coordinators, and services that should release resources when no longer used.

## 6. Common Mistakes

### Mistake 1: Creating a strong reference cycle between two objects

Two classes that point to each other with strong references will never deallocate, even if your app no longer needs them.

**Problem:** Each object keeps the other alive, so ARC cannot drop either reference count to zero.

```swift
class Owner {
    var pet: Pet?
}

class Pet {
    var owner: Owner?
}
```

**Fix:** Make the back-reference `weak` when that reference should not own the object.

```swift
class Owner {
    var pet: Pet?
}

class Pet {
    weak var owner: Owner?
}
```

The corrected version works because `owner` no longer increases the owner's reference count.

### Mistake 2: Using `unowned` when the reference can disappear first

`unowned` is only safe when the referenced object is guaranteed to outlive the reference. If that assumption is wrong, your app can crash at runtime.

**Problem:** Accessing an unowned reference after the target has been deallocated causes a runtime crash.

```swift
class BankAccount {
    var balance: Double = 0
}

class CustomerProfile {
    unowned let account: BankAccount

    init(account: BankAccount) {
        self.account = account
    }
}
```

**Fix:** Use `weak` if the reference may become absent, or redesign ownership so the referenced object truly outlives it.

```swift
class BankAccount {
    var balance: Double = 0
}

class CustomerProfile {
    weak var account: BankAccount?

    init(account: BankAccount) {
        self.account = account
    }
}
```

The corrected version avoids crashes because the reference becomes `nil` instead of pointing to freed memory.

### Mistake 3: Capturing `self` strongly inside a closure

Closures can store strong references to objects they use. If an object stores the closure and the closure stores the object, ARC creates a cycle.

**Problem:** The closure and the object keep each other alive, so `deinit` never runs.

```swift
class Downloader {
    var onComplete: (() -> Void)?

    func start() {
        onComplete = {
            print(self, "Done")
        }
    }
}
```

**Fix:** Capture `self` weakly when the closure is stored by the object itself or may outlive the call.

```swift
class Downloader {
    var onComplete: (() -> Void)?

    func start() {
        onComplete = { [weak self] in
            guard let self = self else { return }
            print(self, "Done")
        }
    }
}
```

The corrected version breaks the cycle because the closure no longer owns the downloader strongly.

## 7. Best Practices

### Practice 1: Use `weak` for back-references and delegates

Back-references usually should not control lifetime. Making them weak prevents accidental ownership loops in common object graphs.

```swift
protocol TaskDelegate: AnyObject {
    func didFinish()
}

class Task {
    weak var delegate: TaskDelegate?
}
```

This keeps the delegate pattern safe and predictable.

### Practice 2: Use `unowned` only with guaranteed ownership

If one object must always outlive another, `unowned` can express that relationship clearly and avoid optional unwrapping. Only use it when the lifetime relationship is guaranteed by design.

```swift
class Country {
    let name: String
    var capitalCity: City?

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

class City {
    let name: String
    unowned let country: Country

    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}
```

This is safe only because the country owns the city structure and outlives it.

### Practice 3: Check closure captures when a property stores the closure

Whenever a class stores a closure property, assume there may be a cycle until you verify the capture list. Capturing `self` weakly is often the safest default.

```swift
class ViewModel {
    var completion: (() -> Void)?

    func load() {
        completion = { [weak self] in
            guard let self = self else { return }
            print(self, "Loaded")
        }
    }
}
```

This avoids surprise leaks and makes the ownership relationship explicit.

## 8. Limitations and Edge Cases

- ARC only manages class instances. Structs and enums are value types and do not use reference counting.
- Strong cycles can also happen through collections, such as arrays or dictionaries that hold class instances in both directions.
- `weak` references must be optional because the referenced object can become `nil` at any time.
- `unowned` references are non-optional, but they crash if accessed after the referenced object is gone.
- Closures often create the most surprising cycles, especially when stored as properties.
- ARC does not solve all memory problems; large cached data, unmanaged resources, and long-lived singletons can still cause high memory usage.
- You cannot rely on `deinit` running if a cycle exists, so cleanup code placed there may never execute until the cycle is broken.

## 9. Practical Mini Project

In this mini project, we will build a tiny library loan system that demonstrates strong ownership, a weak back-reference, and deinitialization when objects go out of scope.

```swift
final class Library {
    let name: String

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

    deinit {
        print("Library released")
    }
}

final class Book {
    let title: String
    weak var library: Library?

    init(title: String, library: Library? = nil) {
        self.title = title
        self.library = library
    }

    deinit {
        print("Book released: \(title)")
    }
}

do {
    let library = Library(name: "City Library")
    let book = Book(title: "ARC in Swift", library: library)

    print(book.title, "belongs to", book.library?.name ?? "no library")
}
```

Output (tested with Swift 6.4):

```text
ARC in Swift belongs to City Library
Library released
Book released: ARC in Swift
```

When the `do` block ends, both objects are released because the book's reference to the library is weak, so there is no cycle.

## 10. Key Points

- ARC automatically releases class instances when their strong reference count reaches zero.
- Strong references keep objects alive, so use them for true ownership.
- Use `weak` for non-owning links that can become `nil`.
- Use `unowned` only when the referenced object is guaranteed to outlive the reference.
- Closures can create retain cycles just like stored properties can.
- Checking `deinit` is a useful way to confirm objects are being released.

## 11. Practice Exercise

- Create two classes, `Teacher` and `Student`.
- Make `Teacher` strongly own a student.
- Make the student's reference to the teacher weak so both objects can be released.
- Add `deinit` messages to both classes.
- Create the objects inside a local scope and verify that both are deinitialized when the scope ends.

**Expected output:** You should see both deinitialization messages after the local scope finishes.

**Hint:** Remember that the back-reference should be optional when it is weak.

Solution:

```swift
final class Teacher {
    let name: String
    var student: Student?

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

    deinit {
        print("Teacher released")
    }
}

final class Student {
    let name: String
    weak var teacher: Teacher?

    init(name: String, teacher: Teacher? = nil) {
        self.name = name
        self.teacher = teacher
    }

    deinit {
        print("Student released")
    }
}

do {
    let teacher = Teacher(name: "Morgan")
    let student = Student(name: "Sam")

    teacher.student = student
    student.teacher = teacher
}
```

Output (tested with Swift 6.4):

```text
Teacher released
Student released
```

## 12. Final Summary

ARC is Swift's built-in memory management system for class instances. It works by tracking strong references and automatically releasing objects when nothing owns them anymore. This gives you predictable, efficient memory management without manual retain and release calls.

The key to using ARC well is understanding ownership. Strong references represent real ownership, weak references break cycles and allow values to disappear safely, and unowned references are for relationships where one object always outlives the other. If you understand those three ideas, most ARC problems become much easier to spot and fix.

As you build more Swift code, watch for retain cycles in delegates, parent-child models, and stored closures. A good next step is to learn more about strong, weak, and unowned references in detail, then practice identifying ownership in your own app's object graphs.
