Hours of light
My blog
Swift uses ARC to manage and track memory usage in applications. Automatic Reference Counting is the full name of ARC. In most cases, developers don’t have to worry about memory management, which ARC automatically frees when instance objects are no longer needed.
ARC reference counts are typically applied to instances or closures of classes, while arrays, dictionaries, strings, structures, and enums are all value types, not stored and passed by reference. The official document reads: Reference counting applies only to instances of classes. Structures and enumerations are value types, Not reference types, and aren’t stored and passed by reference.
For more information on the difference between Value Types and Reference Types, see Swift: Value and Reference Types
1. How does ARC work in Swift
1.1. How ARC Works
- Each time an instance of a class is created, ARC automatically allocates memory for that instance and its associated properties
- ARC frees the memory occupied by the instance when it is no longer in use
- Continuing to access a freed instance, such as calling its methods or properties, may cause the program to crash
- ARC tracks the number of attributes, constants, and variables per reference to the current instance to solve the crash problem caused by accessing the freed instance. ARC does not free this portion of memory as long as there is a valid reference.
- To do this, assign an instance of the class to an attribute (which can also be a constant or variable) one at a time. This property is a strong reference to the instance. It is called a strong reference because the attribute strongly holds the instance and cannot destroy it as long as the strong reference exists.
To illustrate this in code, I have a Student class and set a name property to hold the Student’s name. When I create this class, ARC automatically creates a space for the class to hold the Student instance and its properties.
To better listen for the creation and destruction of this class, I print it in the init and deinit methods, respectively.
class Student: NSObject {
var name: String
init(name: String) {
self.name = name
print("init------------------Student")}deinit {
print("deinit------------------Student")}}Copy the code
var studentTom: Student? = Student(name: "Tom") // The reference count is 1
Copy the code
print("init------------------Student")
Copy the code
Run the code above and you can see the print, which indicates that the reference count is 1. I’m not freeing this memory right now, so what if I just set this instance to nil
studentTom = nil // The reference count is 0
print("deinit------------------Student")
Copy the code
When the deinit method is called and the reference count is zero, ARC automatically frees the instance’s memory.
let studentName = studentTom!.name
Copy the code
When I set studentTom to nil, I call studentTom again and it crashes. Xcode also throws an exception:
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
Copy the code
To prevent this, parsing should generally take precedence when using Optionals.
// Optional binding and unpacking
if let studentTom = studentTom {
let studentName = studentTom.name
}
// guard syntax unpack
guard let studentTom = studentTom else { return }
let studentName = studentTom.name
Copy the code
If I need to copy the student’s information, then the reference count becomes 2. To verify my guess, modify the code as follows:
var studentCopy: Student? = studentTom // The reference count is 2
studentTom = nil // The reference count is 1
Copy the code
When I run the code again, I didn’t call deinit, and when I copy it, its reference count becomes 2, and then I set it to nil and its reference count is 1, and ARC didn’t free its memory. In this case, you need to clear the value of studentCopy and its reference count, and ARC will automatically clean up the memory.
studentCopy = nil // The reference count is 0
deinit------------------Student
Copy the code
2. Circular references
ARC uses strong references to prevent instance objects from being freed prematurely. The answer is no. When two instances form a strong holding loop previously, the memory of the two instances will never be freed. This requires the developer to do something to ensure that the memory can be freed when it is not needed.
2.1. How are circular references generated
-
Circular references occur when two instances hold strong references to each other, such that each instance makes the other’s hold valid.
For example, now I have a teacher class. For the teacher and the student, the teacher needs to know the student’s information, and the student also needs to know the teacher’s information, such as the teacher’s last name, the courses taught and so on.
// Indicates the course taught by the teacher
enum Course {
case language / / the language
case english / / English
case calculus / / calculus
case quantumMechanics // Quantum mechanics
case geology / / geology
}
class Teacher: NSObject {
let lastName: String
let course: Course
var student: Student?
init(lastName: String.course: Course) {
self.lastName = lastName
self.course = course
print("init------------------Teacher")}deinit {
print("deinit------------------Teacher")}}class Student: NSObject {
var name: String
var teacher: Teacher?
init(name: String) {
self.name = name
print("init------------------Student")}deinit {
print("deinit------------------Student")}}Copy the code
var studentTom: Student? = Student(name: "Tom")
var teacherMars: Teacher? = Teacher(lastName: "Mars", course: .calculus)
teacherMars?.student = studentTom
studentTom?.teacher = teacherMars
teacherMars = nil
studentTom = nil
Copy the code
Run the above code and find that the deinit method is not called anyway. Student Tom’s teacher and seacherMars’ student reference each other, and their reference count becomes 2, creating a reference loop. The reference relationship between them is shown below:
2.2. How to avoid circular references
To solve the reference loop problem above, different solutions are adopted depending on whether the attribute is optional. If the attribute is optional, the weak keyword can be used to indicate that the attribute is weak. If an attribute is not optional, you can use the unowned keyword to modify it. Whether weak or unowned, the idea is the same: don’t let some form of reference increase the reference count.
2.2.1 weak references
In the example above, you only need to set weak references to either attribute, or you can set weak to both attributes, but this is not necessary.
weak var student: Student?
Copy the code
The diagram between the two instances is as follows:
What happens when I free up memory for studentTom on a weak reference?
studentTom = nil
teacherMars?.student
print("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\(String(describing: teacherMars?.student))")
Copy the code
init------------------Student
init------------------Teacher
deinit------------------Student
------------------nil
Copy the code
If studentTom is nil, ARC will do something about it.
- First of all,
Student
The object no longer has anystrong reference
Now,ARC
It immediately recycles that memory, and at the same timeTeacher
The object’s reference count is also reduced by one; - Secondly, when
Student
After the object is called back,teacher
thisstrong reference
It doesn’t exist.Teacher
The reference count is reduced by one; - Due to the
student
Is aweak reference
, its value is automatically set tonil
Through theteacherMars? .student
The printed result isnil
You can confirm that.
If I set either of these properties to weak reference and set teacherMars and studentTom to nil, ARC can successfully reclaim all memory, and their relationship looks like this:
teacherMars = nil
studentTom = nil
Copy the code
Print result:
init------------------Student
init------------------Teacher
deinit------------------Student
------------------nil
deinit------------------Teacher
Copy the code
Summary of how to use the weak attribute:
- Weak references do not increase the reference count of an instance and therefore do not prevent ARC from destroying the referenced instance. So with weak references, there is no strong reference ring even if two instances hold each other.
- A weak reference can only be declared as a variable type because its value may change at run time. Weak references must never be declared as constants. In Swift, variables declared with the var keyword are variables declared with the let keyword are constants.
- Because weak references can have no value, variables decorated with weak references must be of optional type.
2.2.3 No Primary Reference
Although weak solves the problem of circular references, not all attributes are optional, so what if a non-nil attribute causes a circular reference?
- I can take this out of the equation
nil
Change the property of thenil
- Another solution that Swift provides for developers is to use an unreferenced reference
Like weak references, an ownerless reference does not enforce instance objects. Unlike weak references, an undirected reference always has a value by default. To declare an undirected reference, add the unowned keyword to attributes and variables.
To demonstrate this process, I added homeWork for each student. Of course, not all students write homeWork on time, so homeWork is optional, and then I implement homeWork class.
// Homework
var homeWork: HomeWork?
Copy the code
class HomeWork: NSObject {
let student: Student
let course: Course
init(student: Student.course: Course) {
self.student = student
self.course = course
print("init------------------HomeWork")}deinit {
print("deinit------------------HomeWork")}}Copy the code
StudentName and course can’t be optional. StudentName and course can’t be optional.
var david: Student? = Student(name: "David Taylor")
var homeWork: HomeWork? = HomeWork(student: david!, course: .quantumMechanics)
Copy the code
This assumes that student David has completed the assignment, which can be represented by the following code:
david?.homeWork = homeWork
Copy the code
init------------------Student
init------------------HomeWork
Copy the code
When the code is run, it is found that the deinit method is not called. At this point, student David and homeWork form a reference cycle. The holding relationship between them is that David and homeWork refer to their own objects respectively, and David and homeWork refer to each other.
Now what if I set David to nil?
david = nil
Copy the code
init------------------Student
init------------------HomeWork
Copy the code
When I run the code, I find that the deinit method is still not called. At this point, although David instance is nil, homeWork instance also leaves its scope. The reference relationship between David. homeWork and homeWork. Student will still keep the two objects in memory, as shown in the figure below:
Of course, you can solve this problem here by using the weak keyword to change any of the strong holds to weak references. Another solution provided by the system can also be used here to solve the circular reference problem by prefixing an attribute of a non-optional type with unowned, no main reference.
unowned let student: Student
Copy the code
We can solve this problem by adding unowned before any attribute of a Strong reference. The only difference is that the Strong reference is changed to unowned reference, and the reference relationship between them is as follows:
Run the code again:
david = nil
homeWork = nil
Copy the code
The print result is as follows:
init------------------Student
init------------------HomeWork
deinit------------------Student
deinit------------------HomeWork
Copy the code
It can be seen that both David and homeWork can be recycled normally. When David is nil, the Student object will be recycled by ARC, and when homeWork is nil, homeWork will lose its role and be recycled by ARC.
What happens if I call the freed memory? Modify the code as follows:
homeWork = nil homeWork! .studentCopy the code
The program will crash and prompt:
Unexpectedly found nil while unwrapping an Optional value
Copy the code
So while using unowned solves the problem of non-optional attribute circular references, in practice you should also be careful to ensure that the reference always points to an undestroyed instance when using an undirected reference.
Although weak and unowned solve the problem of strong reference loops in Class, Class is not the only reference type in Swift. Closure is also a reference type in Swift. Refer to the swift Programming Language: Closures.
Related links:
The Swift Programming Language Automatic Reference Counting
Handling non-optional optionals in Swift
Automatic reference counting in Swift
Understand custom types for reference semantics
In this paper, the demo