top of page

Memory Management in Swift: ARC, Weak, and Unowned References

  • Writer: arda doğantemur
    arda doğantemur
  • Jun 5, 2024
  • 3 min read



Memory management is a crucial aspect of any programming language, and Swift handles it gracefully through a mechanism called Automatic Reference Counting (ARC). Understanding how ARC works and when to use weak and unowned references can help you write more efficient and safer Swift code.


What is Automatic Reference Counting (ARC)?

Automatic Reference Counting (ARC) is a memory management feature of Swift that automatically keeps track of the memory used by your app. ARC ensures that instances of classes are only kept in memory as long as they are needed, and it automatically frees up memory when those instances are no longer needed.


How ARC Works

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. This includes properties, methods, and any other data. ARC then keeps a count of the number of references to each class instance. When the reference count drops to zero, ARC deallocates the instance and frees up the memory.


Here's a simple example:


class Person {
    let name: String
    
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var person1: Person? = Person(name: "John")
var person2 = person1

person1 = nil
person2 = nil

In this example, the Person instance is created and assigned to person1. When person1 is set to nil, the reference count decreases but does not reach zero because person2 still holds a reference. When person2 is also set to nil, the reference count drops to zero, and ARC deallocates the Person instance.


Strong References and Retain Cycles

By default, every reference in Swift is a strong reference, meaning that it increases the reference count of the instance it points to. However, this can lead to retain cycles, where two or more instances hold strong references to each other, preventing ARC from deallocating them.


class Person {
    let name: String
    var apartment: Apartment?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil
unit4A = nil

In this scenario, john and unit4A both hold strong references to each other, resulting in a retain cycle. Neither instance is deallocated because their reference counts never reach zero.


Weak References

To break retain cycles, you can use weak references. A weak reference does not increase the reference count of the instance it refers to. Weak references are declared using the weak keyword and must always be optional (nil when the referenced instance is deallocated).

Here's how you can break the retain cycle using weak references:


class Person {
    let name: String
    var apartment: Apartment?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person?
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil
unit4A = nil

Now, when john is set to nil, the reference count of Person drops to zero, and the instance is deallocated. Consequently, the weak reference tenant in Apartment is set to nil, breaking the cycle.


Unowned References

Unowned references are similar to weak references but are used when the referenced instance is expected to always have a value. Unlike weak references, unowned references are not optional and do not become nil when the referenced instance is deallocated. Declaring an unowned reference uses the unowned keyword.

Use unowned references when the referenced instance is expected to outlive the reference. If the referenced instance is deallocated and the unowned reference is accessed, a runtime crash will occur.

Here's an example using unowned references:


class Customer {
    let name: String
    var card: CreditCard?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

class CreditCard {
    let number: String
    unowned let customer: Customer
    
    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    
    deinit {
        print("CreditCard \(number) is being deinitialized")
    }
}

var john: Customer? = Customer(name: "John")
john?.card = CreditCard(number: "1234-5678-9012-3456", customer: john!)

john = nil

in this case, CreditCard holds an unowned reference to Customer, and when john is set to nil, both Customer and CreditCard instances are deallocated correctly.


Feature

Weak Reference

Strong Reference

Unowned Reference

Reference Count

Does not increase reference count

Increases reference count

Does not increase reference count

Deallocation

Automatically set to nil when the referenced instance is deallocated

Keeps the referenced instance alive

Can cause a runtime crash if accessed after the referenced instance is deallocated

Use Case

Used for properties that can become nil during the lifecycle (e.g., delegates, parent-child relationships)

Default type of reference, used for most references

Used when the referenced instance is expected to outlive the referencing instance

Syntax

weak var reference: Type

var reference: Type

unowned var reference: Type

Example Scenario

Breaking retain cycles where the referenced instance may become ni

Default references for most class properties

References in a closure capturing a property where the captured property will outlive the closure



Conclusion

Understanding ARC, strong, weak, and unowned references is essential for effective memory management in Swift. Properly using these concepts helps prevent memory leaks and ensures that your applications run efficiently. By mastering these techniques, you can write safer and more reliable Swift code.

Comments


© 2020 by Arda Doğantemur.

bottom of page