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,StudentThe object no longer has anystrong referenceNow,ARCIt immediately recycles that memory, and at the same timeTeacherThe object’s reference count is also reduced by one;
  • Secondly, whenStudentAfter the object is called back,teacherthisstrong referenceIt doesn’t exist.TeacherThe reference count is reduced by one;
  • Due to thestudentIs aweak reference, its value is automatically set tonilThrough theteacherMars? .studentThe printed result isnilYou 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 equationnilChange 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