Swift Automatic Reference Count (ARC)

Swift uses automatic reference counting (ARC) as a mechanism to track and manage the memory of an application

Typically, we don't need to free up memory manually, because ARC automatically frees up the memory that an instance of a class uses when it is no longer in use.

But sometimes we still need to implement memory management in our code.

ARC features

  • Each time a new instance of a class is created using the init() method, the ARC allocates a large chunk of memory to store the instance's information.

  • The memory contains the type information for the instance and the value of all the related properties of the instance.

  • When an instance is no longer in use, the ARC frees up the memory occupied by the instance and allows the freed memory to be diverted for other purposes.

  • To ensure that instances in use are not destroyed, ARC tracks and calculates how many attributes, constants, and variables each instance is being referenced.

  • Instances are assigned to properties, constants, or variables, all of which create strong references to this instance, which are not allowed to be destroyed as long as the strong references are still in.

An ARC instance

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) 开始初始化")
    }
    deinit {
        print("\(name) 被析构")
    }
}

// 值会被自动初始化为nil,目前还不会引用到Person类的实例
var reference1: Person?
var reference2: Person?
var reference3: Person?

// 创建Person类的新实例
reference1 = Person(name: "W3CSchool")


//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1

//断开第一个强引用
reference1 = nil
//断开第二个强引用
reference2 = nil
//断开第三个强引用,并调用析构函数
reference3 = nil

The output of the above program execution is:

W3CSchool 开始初始化
W3CSchool 被析构

The loop between class instances is highly referenced

In the example above, ARC tracks the number of references to your newly created Person instance and destroys it when the Person instance is no longer needed.

However, we might write code like this, and a class will never have 0 strong references. T his occurs when two class instances maintain each other's strong references and keep each other from being destroyed. This is called a loop strong reference.

Instance

Here's an example of a looping strong reference that is unthittingly generated. The example defines two classes: Person and Partment, which are used to model the apartment and its inhabitants:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) 被析构") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { print("Apartment #\(number) 被析构") }
}

// 两个变量都被初始化为nil
var youj: Person?
var number73: Apartment?

// 赋值
youj = Person(name: "W3CSchool")
number73 = Apartment(number: 73)

// 意感叹号是用来展开和访问可选变量 youj 和 number73 中的实例
// 循环强引用被创建
youj!.apartment = number73
number73!.tenant = youj

// 断开 youj 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
youj = nil
number73 = nil

Resolves looping strong references between instances

Swift provides two ways to solve the looping strong reference problem you encounter when using the properties of a class:

  • Weak reference
  • No primary reference

Weak and masterless references allow one instance in a circular reference to reference another instance without maintaining a strong reference. T his allows instances to reference each other without generating a looping strong reference.

Use weak references for instances that become nil in the lifecycle. C onversely, for instances that are no longer assigned nil after initializing an assignment, no master reference is used.

A weak reference instance

class Module {
    let name: String
    init(name: String) { self.name = name }
    var sub: SubModule?
    deinit { print("\(name) 主模块") }
}

class SubModule {
    let number: Int
    
    init(number: Int) { self.number = number }
    
    weak var topic: Module?
    
    deinit { print("子模块 topic 数为 \(number)") }
}

var toc: Module?
var list: SubModule?
toc = Module(name: "ARC")
list = SubModule(number: 4)
toc!.sub = list
list!.topic = toc

toc = nil
list = nil

The output of the above program execution is:

ARC 主模块
子模块 topic 数为 4

No primary reference instance

class Student {
    let name: String
    var section: Marks?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("\(name)") }
}
class Marks {
    let marks: Int
    unowned let stname: Student
    
    init(marks: Int, stname: Student) {
        self.marks = marks
        self.stname = stname
    }
    
    deinit { print("学生的分数为 \(marks)") }
}

var module: Student?
module = Student(name: "ARC")
module!.section = Marks(marks: 98, stname: module!)
module = nil

The output of the above program execution is:

ARC
学生的分数为 98

Loop strong references caused by closures

Loop strong references also occur when you assign a closure to a property of a class instance, and the instance is used in that closure. T his closure may have accessed a property of the instance, such as self.someProperty, or a method of the instance called in the closure, such as self.someMethod. Both cases result in a closure "capture" self, resulting in a looping strong reference.

Instance

The following example shows you how a loop strong reference is generated when a closed-loop reference is made to self. The example defines a class called HTMLElement, which represents a separate element in HTML with a simple model:

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())

The HTMLElement class produces a looping strong reference between the class instance and the closure of the asHTML default.

The instance's asHTML property holds a strong reference to the closure. H owever, the closure uses self (references to self.name and self.text) in its closure body, so the closure captures the self, which in turn means that the closure in turn holds a strong reference to the HTMLElement instance. This results in a looping strong reference for both objects.

Resolve loop strong references caused by closures: The capture list is defined as part of the closure at the same time when the closure is defined, which resolves the loop strong references between the closure and the class instance.


Weak and masterless references

When the closed and captured instances are always referenced to each other and are always destroyed at the same time, the capture within the closure is defined as a masterless reference.

Conversely, when a capture reference can sometimes be nil, the capture within the closure is defined as a weak reference.

If the captured reference is never set to nil, it should be a masterless reference instead of a weak reference.

Instance

In the previous HTMLElement example, a masterless reference is the correct way to solve a loop-strong reference. Write HTMLElement classes in this way to avoid looping strong references:

class HTMLElement {
    
    let name: String
    let text: String?
    
    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) 被析构")
    }
    
}

//创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

// HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息
paragraph = nil

The output of the above program execution is:

<p>hello, world</p>
p 被析构