Swift Automatic Reference Counting (ARC)
Swift uses Automatic Reference Counting (ARC) to track and manage your app's memory usage.
In most cases, we don't need to manually release memory, because ARC automatically frees the memory occupied by a class instance when that instance is no longer used.
But in some cases, we still need to implement memory management in code.
How ARC Works
Every time you use init() to create a new instance of a class, ARC allocates a chunk of memory to store information about the instance.
This memory contains the type information of the instance, as well as the values of all its related properties.
When the instance is no longer used, ARC frees the memory occupied by the instance and makes the freed memory available for other purposes.
To ensure that instances in use are not destroyed, ARC tracks and calculates how many properties, constants, and variables are currently referencing each instance.
When an instance is assigned to a property, constant, or variable, they create a strong reference to the instance. As long as a strong reference still exists, the instance is not allowed to be destroyed.
ARC Example
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: "Example")
//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1
//断开第一个强引用
reference1 = nil
//断开第二个强引用
reference2 = nil
//断开第三个强引用,并调用析构函数
reference3 = nil
The output of the above program is:
Example 开始初始化 Example 被析构
Strong Reference Cycles Between Class Instances
In the above example, ARC tracks the reference count of the Person instance you create and destroys it when the Person instance is no longer needed.
However, we might write code where a class never has zero strong references. This happens when two class instances hold strong references to each other, keeping each other from being destroyed. This is called a strong reference cycle.
Example
The following shows an example of accidentally creating a strong reference cycle. The example defines two classes: Person and Apartment, which 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 example: Person?
var number73: Apartment?
// 赋值
example = Person(name: "Example")
number73 = Apartment(number: 73)
// 意感叹号是用来展开和访问可选变量 example 和 number73 中的实例
// 循环强引用被创建
example!.apartment = number73
number73!.tenant = example
// 断开 example 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
example = nil
number73 = nil
Resolving Strong Reference Cycles Between Class Instances
Swift provides two ways to resolve the strong reference cycle problem you encounter when using class properties:
- Weak references
- Unowned references
Weak and unowned references allow one instance in a reference cycle to refer to another instance without keeping a strong reference. This allows instances to refer to each other without creating a strong reference cycle.
Use a weak reference for instances that can become nil during their lifetime. Conversely, use an unowned reference for instances that, once assigned during initialization, will never be set to nil again.
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 of the above program 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 of the above program is:
ARC 学生的分数为 98
Strong Reference Cycles Caused by Closures
A strong reference cycle can also occur when you assign a closure to a property of a class instance, and the closure body uses the instance. The 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 both cases, the closure "captures" self, resulting in a strong reference cycle.
Example
The following example shows how a strong reference cycle is created when a closure references self. The example defines a class called HTMLElement, which models a single HTML element 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 creates a strong reference cycle between the class instance and the closure used as the default value of asHTML.
The instance's asHTML property holds a strong reference to the closure. However, the closure uses self in its body (referencing self.name and self.text), so the closure captures self, which means the closure in turn holds a strong reference to the HTMLElement instance. This creates a strong reference cycle between the two objects.
Resolving Strong Reference Cycles Caused by Closures: Define a capture list as part of the closure when defining the closure. In this way, you can resolve the strong reference cycle between the closure and the class instance.
Weak and Unowned References
When the closure and the captured instance always refer to each other and are always destroyed at the same time, define the capture in the closure as an unowned reference.
Conversely, when the captured reference may sometimes be nil, define the capture in the closure as a weak reference.
If the captured reference will never be set to nil, you should use an unowned reference rather than a weak reference.
Example
In the previous HTMLElement example, an unowned reference is the correct way to resolve the strong reference cycle. Write the HTMLElement class this way to avoid the strong reference cycle:
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 is:
<p>hello, world</p> p 被析构Other Extensions