Strong Reference Cycle
Using weak variables.
Strong Reference Cycle
Using weak variables.
0
0
Checkbox to mark video as read
Mark as read

Memory management is a crucial aspect of any programming language. In Swift, memory management is primarily handled through Automatic Reference Counting (ARC). ARC keeps track of how many instances of a class are being referenced. However, strong reference cycles can cause memory leaks, which can prevent objects from being deallocated properly. In this article, we'll explore what a strong reference cycle is, how it happens, and how to resolve it in Swift.

What is a Strong Reference?

In Swift, by default, references between class instances are considered strong. A strong reference means that the referenced object cannot be deallocated as long as it is being held by another object. ARC counts the number of strong references to a given instance. When that count drops to zero, ARC deallocates the instance and frees the memory.

Strong references are helpful in ensuring that instances remain in memory as long as they are needed. However, they can lead to an issue called a strong reference cycle, which causes memory leaks.

What is a Strong Reference Cycle?

A strong reference cycle occurs when two or more instances of classes hold strong references to each other, preventing ARC from deallocating them. Since both instances are referencing each other, their reference count never drops to zero, causing a memory leak. This typically happens when objects are interconnected, especially in situations involving closures or delegate patterns.

Example of a Strong Reference Cycle

Let's consider an example of two class instances that reference each other, leading to a strong reference cycle:

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 example:

  • Person has a strong reference to Apartment via the apartment property.
  • Apartment also has a strong reference back to Person via the tenant property.

When we set john and unit4A to nil, the expected behavior is that both instances are deallocated. However, the reference cycle prevents this, as both instances still hold strong references to each other. This leads to a memory leak, and the deinit methods are not called.

Breaking a Strong Reference Cycle with Weak and Unowned References

To resolve strong reference cycles, Swift provides two types of references that don't increment the reference count: weak and unowned references.

1. Weak References

A weak reference does not increment the reference count of the object it refers to. This allows the object to be deallocated even though a weak reference still exists. A weak reference is always optional and automatically set to nil when the referenced object is deallocated.

class Apartment {
    let unit: String
    weak var tenant: Person? // weak reference to break the cycle

    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 modified version, the tenant property in Apartment is declared as a weak reference. This breaks the strong reference cycle, allowing both instances to be deallocated properly when set to nil.

2. Unowned References

An unowned reference also does not increase the reference count, similar to a weak reference. However, unlike weak references, an unowned reference is non-optional and is expected to always have a valid value. Use unowned references when both instances will have the same lifetime, or when one instance will always outlive the other.

class Apartment {
    let unit: String
    unowned var tenant: Person // unowned reference

    init(unit: String, tenant: Person) {
        self.unit = unit
        self.tenant = tenant
    }

    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

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

john = nil
unit4A = nil

In this case, the unowned reference is used to indicate that Apartment does not keep a strong hold on Person, allowing both to be deallocated when no longer needed. You should use unowned references when it is certain that the reference will never be nil during its lifetime.

course

Quiz Time!

0 Comments

Join the community to comment
Sign Up
I have an account
Be the first to comment

Accept Cookies

We use cookies to collect and analyze information on site performance and usage, in order to provide you with better service.

Check our Privacy Policy