Swift automatic reference counting


Swift uses the automatic reference counting (ARC) mechanism to track and manage the application's memory

Normally we do not need to manually release the memory, because ARC will be used when the instance of the class is no longer When used again, the memory occupied by it is automatically released.

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

ARC Function

  • Whenever you use the init() method to create a new instance of a class, ARC will allocate a large piece of memory to store the instance. information.

  • The memory will contain the type information of the instance and the values ​​of all related attributes of this instance.

  • When the instance is no longer in use, ARC releases the memory occupied by the instance and allows the released memory to be used for other purposes.

  • To ensure that instances in use are not destroyed, ARC will track and calculate how many properties, constants and variables are being referenced by each instance.

  • Assigning an instance to a property, constant or variable will create a strong reference to the instance. As long as the strong reference exists, the instance is not allowed to be destroyed.

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: "php")


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

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

The execution output of the above program is:

php 开始初始化
php 被析构

Strong circular references between class instances

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

However, we may write code such that a class will never have 0 strong references. This happens when two class instances keep strong references to each other and prevent each other from being destroyed. This is called a strong reference cycle.

Example

The following shows an example of inadvertently generating a strong reference cycle. The example defines two classes: Person and Apartment, which are used to model an apartment and its residents:

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 php: Person?
var number73: Apartment?

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

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

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

Resolving strong circular references between instances

Swift provides two methods. To solve the cyclic strong reference problem you encounter when using class attributes:

  • Weak reference

  • Unowned reference

Weak references and unowned references allow one instance in a circular reference to refer to another instance without maintaining a strong reference. In this way, instances can refer to each other without creating a strong reference cycle.

Use weak references for instances that will become nil during the life cycle. On the contrary, use an unowned reference for an instance that will never be assigned to nil after the initial assignment.

Weak Reference Example

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 result of the above program execution is:

ARC 主模块
子模块 topic 数为 4

Unowned Reference Example

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 result of the above program execution is:

ARC
学生的分数为 98

Strong reference cycle caused by closure

Strong reference cycle also occurs when you assign a closure to a property of a class instance, and the closure body contains another Examples are used. This closure body may access a property of the instance, such as self.someProperty, or call a method of the instance, such as self.someMethod, in the closure. Both cases result in the closure "capturing" self, creating a strong reference cycle.

Example

The following example shows you how to generate a strong reference cycle when a closure refers to self. The example defines a class called HTMLElement, which uses a simple model to represent a single element in HTML:

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 generates a strong loop between the class instance and the closure of the asHTML default value. Quote.

The asHTML attribute of the instance holds a strong reference to the closure. However, the closure uses self in its closure body (referencing self.name and self.text), so the closure captures self, which means that the closure in turn holds a strong reference to the HTMLElement instance. In this way, the two objects have a strong circular reference.

Resolve the strong reference cycle caused by the closure: When defining the closure, define the capture list as part of the closure. In this way, the strong reference cycle between the closure and the class instance can be solved.


Weak References and Unowned References

Define a capture within a closure as unowned when the closure and captured instances always refer to each other and are always destroyed at the same time. Quote.

On the contrary, when the capturing reference may sometimes be nil, define the capture within the closure as a weak reference.

If the captured reference will never be set to nil, an unowned reference should be used instead of a weak reference.

Example

In the previous HTMLElement example, unowned reference is the correct way to solve the strong reference cycle. Write the HTMLElement class like this to avoid strong reference cycles:

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 result of the execution of the above program is:

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