✍️ Note

All the codes and contents are sourced from Apple’s official documentation. This post is for personal notes where I summarize the original contents to grasp the key concepts

Apple document

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

Check Strong Reference Count

Example 1

var person = Person(name: "Shawn") //1
CFGetRetainCount(person) //2

Example 2

CFGetRetainCount(Person(name: "Shawn") //1

Example 3

var person = Person(name: "Shawn") //2
var person2 = person //3
var person3 = person //4

CFGetRetainCount(person) //4

As you can see when you initiate an object, the retain count is 1. And when you assign it, retain count will be counting up.

Strong Reference Cycle between classes

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


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

Example 1

var person = Person(name: "Shawn")
var apt = Apartment(unit: "3")

CFGetRetainCount(person) //2
CFGetRetainCount(apt) //2

Example 2 (🔥 Strong Reference Cycle)

var person = Person(name: "Shawn")
var apt = Apartment(unit: "3")

person.apartment = apt
apt.tenant = person

CFGetRetainCount(person) //3 (apt.tenant -> count up)
CFGetRetainCount(apt) //3 (person.apartment -> count up)

✅ Solution -> weak / unowned

What is weak reference and when to use it?

Use a weak reference when the other instance has a shorter lifetime — that is, when the other instance can be deallocated first.

Apple – https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated. And, because weak references need to allow their value to be changed to nil at runtime, they’re always declared as variables, rather than constants, of an optional type.

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


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

What is unowned reference and when to use it?

In contrast, use an unowned reference when the other instance has the same lifetime or a longer lifetime. Unlike a weak reference, an unowned reference is expected to always have a value. As a result, marking a value as unowned doesn’t make it optional, and ARC never sets an unowned reference’s value to nil.

Important

Use an unowned reference only when you are sure that the reference always refers to an instance that hasn’t been deallocated.

If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.

Apple – https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting
class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}


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

Example 1

var customer: Customer! = Customer(name: "Shawn")
customer.card = CreditCard(number: 7788_9999_1223_1225, customer: customer)

CFGetRetainCount(customer) //2 (because CreditCard's customer is defined as unowned let)

customer = nil

//Shawn is being deinitialized
//Card #7788999912231225 is being deinitialized 

unowned(unsafe)

Swift also provides unsafe unowned references for cases where you need to disable runtime safety checks — for example, for performance reasons. As with all unsafe operations, you take on the responsibility for checking that code for safety. You indicate an unsafe unowned reference by writing unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

When you access the unowned(unsafe), It try to access the memory location where the instance used to be

when use unowned, error type is different.

Unowned Optional References

You can mark an optional reference to a class as unowned. In terms of the ARC ownership model, an unowned optional reference and a weak reference can both be used in the same contexts. The difference is that when you use an unowned optional reference, you’re responsible for making sure it always refers to a valid object or is set to nil.

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting
class Department {
    var name: String
    var courses: [Course]
    init(name: String) {
        self.name = name
        self.courses = []
    }
}


class Course {
    var name: String
    unowned var department: Department
    unowned var nextCourse: Course?
    init(name: String, in department: Department) {
        self.name = name
        self.department = department
        self.nextCourse = nil
    }
}

Example

var department: Department! = Department(name: "Horticulture")

let intro = Course(name: "Survey of Plants", in: department)
var intermediate: Course! = Course(name: "Growing Common Herbs", in: department)
var advanced: Course = Course(name: "Caring for Tropical Plants", in: department)

intro.nextCourse = intermediate
intermediate.nextCourse = advanced
department.courses = [intro, intermediate, advanced]

//Deinit department and intermediate
department = nil
intermediate = nil

//Try to access nextCourse
intro.nextCourse //SIGABRT Error

Unlike weak, when you try to access the deallocated object, It causes a crash issue. Even you set it as unowned optionals.

Unowned References and Implicitly Unwrapped Optional Properties

class Country {
    let name: String
    var capitalCity: City! //Unwrapped Optional Properties
    init(name: String, capitalName: String) {
        self.name = name
        //At this point, Country already initiated. So can pass self to City initializer
        self.capitalCity = City(name: capitalName, country: self)
    }
}


class City {
    let name: String
    unowned let country: Country //Unowned References
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

var capitalCity: City!

  • It’s initial value is nil

Above the example is Two-Phased initialization

🤔 Strong Reference Cycles for Closures

It’s such an important topic.

A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance. This capture might occur because the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.

This strong reference cycle occurs because closures, like classes, are reference types. When you assign a closure to a property, you are assigning a reference to that closure. In essence, it’s the same problem as above — two strong references are keeping each other alive. However, rather than two class instances, this time it’s a class instance and a closure that are keeping each other alive.

Swift provides an elegant solution to this problem, known as a closure capture list

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting
class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

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

    deinit {
        print("\(name) is being deinitialized")
    }
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

//Deallocate HTMLElement
paragraph = nil // Can't deallocate it because the reference cycle between property (asHTML) and closure's capture list

Even though the closure refers to self multiple times (self.text and self.name), it only captures one strong reference to the HTMLElement instance.

Apple

Resolving Strong Reference Cycles for Closures

Use weak or unowned at capture lists

Defining a capture list

Each item in a capture list is a pairing of the weak or unowned keyword with a reference to a class instance (such as self) or a variable initialized with some value (such as delegate = self.delegate). These pairings are written within a pair of square braces, separated by commas.

Place the capture list before a closure’s parameter list and return type if they’re provided:

Apple
lazy var someClosure = {
        [unowned self, weak delegate = self.delegate]
        (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}


lazy var someClosure2 = {
        [unowned self, weak delegate = self.delegate] in
    // closure body goes here
}

🔥 Think it again, weak vs unowned in Closures

Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.

Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.

Apple
class HTMLElement {
    let name: String
    let text: String?
    //Use unowned self to break reference cycle
    lazy var asHTML: () -> String = {
            [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

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

    deinit {
        print("\(name) is being deinitialized")
    }
}

Capture value from nested Closure

class Test {}
DispatchQueue.global().async {
  let test = Test()
   print("closure A: Start retain count is: \(CFGetRetainCount(test))")
   DispatchQueue.global().async {
     print("nested closure B retainCount: \(CFGetRetainCount(test))")
   }
   print("closure A: End")
}

//Prints
closure A: Start retain count is: 2
closure A: End
nested closure B retainCount: 3

As you can see nested closure also counting up ARC.

But… what about this example?

test object was already deallocated. And when you trying to access test, It should not be nil because It’s unowned.

Conclusion

Understanding How ARC works is very important to avoid memory leaks and crash issues. It’s also good to know what happens when objects are deallocated and How ARC handles those objects.

Leave a comment

Quote of the week

"People ask me what I do in the winter when there's no baseball. I'll tell you what I do. I stare out the window and wait for spring."

~ Rogers Hornsby