[Swift learning] Swift programming tour --- ARC (20), swift --- arc
Swift uses automatic reference count (ARC) to track and manage the memory used by the application. In most cases, this means that in the Swift language, memory management is "still working" and you do not need to consider memory management on your own. When an instance is no longer in use, ARC automatically releases the memory occupied by these class instances. However, in rare cases, to automatically manage memory space, ARC needs to learn more about the relationship between your code snippets. This chapter describes these situations and shows you how to enable ARC to manage all the memory space of an application.
Note: The reference count is only applied to instances of the class. Structure and enumeration types are value types, not reference types, and are not stored and transmitted in reference mode.
How ARC WorksEach time a class instance is created, ARC allocates a memory block to store information about the instance. This memory block stores the instance type and the value of attributes related to this instance. When the instance is no longer used, ARC releases the memory used by the instance so that the memory can be used. This ensures that class instances are no longer used, and they do not occupy memory space. However, if ARC releases an instance that is still in use, you cannot access the properties of the instance or call its method. If you still try to access this instance, the application is very likely to crash. To ensure that the preceding conditions are not met, ARC tracks attributes, constants, and variable quantities related to the instance of the class. As long as there is a valid reference, ARC will not release this instance. To make this happen, if you assign an instance of a class to an attribute, constant, or variable, this attribute, constant, or variable is the strong reference of this instance ). It is called a "strong" reference because it holds the instance and cannot destroy the instance as long as the instance still exists. The following example shows how ARC works. This example defines a simple class with the class name "Person" and a constant attribute named name.
class Person { let name: String init(name: String) { self.name = name println("\(name) is being initialized") } deinit { println("\(name) is being deinitialized") } }
The following code snippet defines three persons? Type variables. These variables are used to create multiple references. These references reference the Person object created by the followed code. Because these variables are optional (Person ?, So they are automatically initialized as nil, and there is no reference to a Person instance.
var reference1: Person? var reference2: Person? var reference3: Person?
Now we create a new Person instance and assign it to one of the above three variables:
reference1 = Person(name: "John Appleseed") // prints "Jonh Appleseed is being initialized"
Because the Person instance is assigned the reference1 variable, reference1 is a strong reference of the Person instance. Because at least this strong reference exists, ARC ensures that this instance will be stored in the memory and will not be destroyed. If you assign the Person instance to the other two variables, the other two strong references pointing to the instance will be created:
reference2 = reference1 reference3 = reference2
Now, this Person instance has three strong references. If you assign nil to two variables to destroy two strong references (including the original references), only one strong reference is left, and the Person instance will not be destroyed:
reference1 = nil reference2 = nil
The ARC will not destroy the Person instance until the third and last strong references are destroyed. At this time, it is very clear that you cannot continue to use the Person instance:
Referenece3 = nil // print "John Appleseed is being deinitialized"
Strong reference loop between class instancesThis happens when two class instances maintain strong references from each other so that each instance keeps the other instance valid. We call it a strongly referenced loop. The following example shows how a strongly referenced ring is generated inadvertently. The example defines two classes, namely 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 { println("\(name) is being deinitialized") } } class Apartment { let unit: Int init(unit: Int) { self.unit= unit } var tenant: Person? deinit { println("Apartment #\(number) is being deinitialized") } }
Each Person instance has a name attribute of the String type and an optional attribute of the apartment initialized as nil. Apartment properties are optional, because a person does not necessarily own an apartment. Similarly, each Apartment instance has an Int type number attribute and a tenant optional attribute initialized as nil. Tenant attributes are optional because an apartment does not necessarily have residents. Both classes also define initialization functions, and the print message indicates that the instance of this class is being initialized. This allows you to see whether the Person and Apartment instances are destroyed as expected. The following code snippet defines two optional types of variables, john and number73, which are assigned to specific Apartment and Person instances respectively. Thanks to the advantages of optional types, the initial values of these two variables are nil:
var john: Person? var unit4A: Apartment?
Now, you can create a specific Person instance and Apartment instance and assign them to john and number73:
jhon = Person(name: "John Appleseed") unit4A = Apartments(number: 4A)
The following figure shows the strong reference relationship after the two instances are created and assigned values. John has a strong reference for a Person instance and unit4A has a strong reference for an Apartment instance:
Now you can associate two instances. One person owns an apartment and the other has a tenant. Note: use an exclamation point (!) To expand and access optional types of variables. Only in this way can these variables be assigned values:
john!.apartment = unit4Aunit4A!.tenant = john
Shows the strong reference relationship after the instance is associated.
The two instances are associated to generate a strong circular reference. The Person and Apartment instances each hold a strong reference from each other. Therefore, even if you destroy the strong references held by john and number73, the reference count will not change to 0, so ARC will not destroy these two instances.
john = nilunit4A = nil
When the values of the preceding two variables are nil, no destructor is called. Strong references prevent the destruction of Person and Apartment instances, further leading to memory leakage.
Avoid a strong reference Loop
Swift provides two methods to avoid a strong reference loop: weak reference and non-hold reference.
Weak references are used for instances that will change to nil during the life cycle, and instances that are no longer assigned nil after the value is assigned during initialization, use non-hold references.
Weak reference
The weak reference does not increase the reference count of the instance, so it does not prevent ARC from destroying the referenced instance. When declaring attributes or variables, the keyword weak indicates that the reference is a weak reference. A weak reference can only be declared as a variable, because its value may change during runtime. Weak references cannot be declared as constants.
Weak references can have no value, so the declared weak references must be of an optional type. In Swift, it is recommended that you use an optional type as a reference type that may have no value.
The following example is similar to the previous Person and Apartment example, except for an important difference. This time, we declare that the tenant attribute of Apartment is weak reference:
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") }} class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") }}
Create a strong reference of two variables (john and unit4A) and associate these two instances:
var john: Person?var unit4A: Apartment? john = Person(name: "John Appleseed")unit4A = Apartment(unit: "4A") john!.apartment = unit4Aunit4A!.tenant = john
The referenced relationship diagram is as follows:
The Person instance is still a strong reference of the Apartment instance, but the Apartment instance is a weak reference of the Person instance. This means that when the strong reference held by the john variable is destroyed, there will no longer be any strong reference of the Person instance:
Since there is no strong reference to the Person instance, the instance will be destroyed:
Non-hold referenceSimilar to weak references, non-hold references do not hold instances. However, unlike weak references, non-hold references always have a value by default. Therefore, a non-hold reference can only be defined as a non-optional type (non-optional type ). Add the unowned keyword before attributes and variables to declare a non-hold reference. Because it is not an optional type, you can directly access it without expanding it when using a non-hold reference. However, non-optional variables cannot be assigned nil values. Therefore, when an instance is destroyed, ARC cannot assign a reference value to nil.
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { println("\(name) is being deinitialized") } class CreditCard { let number: Int unowned let customer: Customer init(number: Int, customer: Customer) { self.number = number self.customer = customer } deinit { println("Card #\(number) is being deinitialized") }
The following code defines an optional type of Customer variable named john, which is used to save the reference of a specific consumer. Because it is a mutable type, the initial value of this variable is nil:
var john: Customer?
Create a Customer instance, use it to initialize the CreditCard instance, and assign the created CreditCard instance to the Customer's card attribute:
john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer:john!)
The reference relationship is shown in.
John does not hold a reference to the CreditCard instance. When the strong reference held by the john variable is damaged, there is no strong reference to the Customer instance.
The Customer instance is destroyed. Then, the strong reference of the CreditCard instance no longer exists, so the CreditCard instance is also destroyed.
John = nil // print "John Appleseed is being deinitialized" // print "Card #1234567890123456 is being deinitialized"
Optional attributes that do not hold a reference or expand implicitly
The example of Person and Apartment illustrates the following scenario: the values of both attributes may be nil, and a strongly referenced ring may be generated. In this scenario, weak references are applicable. The example of Customer and CreditCard illustrates another scenario: one attribute can be nil, the other attribute cannot be nil, and a strongly referenced ring may be generated. In this scenario, it is suitable for the use of non-primary references. However, there is a third scenario: Both attributes must have values and cannot be nil after initialization. In this scenario, one class uses the non-primary reference attribute, and the other class uses the optional property that is implicitly expanded. In this way, after Initialization is complete, we can immediately access these two variables (without the need to expand them)
The following example defines two classes, Country and City, each of which has an attribute to save instances of another class. In this model, every country has a capital, and every city belongs to a country. Therefore, the Country class has a capitalCity attribute, and the City class has a country attribute:
class Country { let name: String let capitalCity: City! init(name: String, capitalName: String) { self.name = name self.capitalCity = City(name: capitalName, country: self) } } class City { let name: String unowned let country: Country init(name: String, country: Country) { self.name = name self.country = country } }
The City initialization function has a Country instance parameter and uses the country attribute to store this instance. In this way, the relationship described above is realized. The Country initialization function calls the City initialization function. However, only after the Country instance is fully initialized (in Two-Phase Initialization) Can the Country Initialization Function Pass self to the City Initialization function. To meet this requirement, add an exclamation point (City!) at the end of the type !), We declare that the capitalCity attribute of Country is an optional property that is implicitly expanded. That is to say, the default value of the capitalCity attribute is nil, which can be accessed directly without expanding its value (described in Implicity Unwrapped Optionals. Because the default value of capitalCity is nil, once the Country instance assigns a value to the name attribute during initialization, the entire initialization process is complete. This means that after the name attribute is assigned, the Country initialization function can reference and pass the implicit self. Therefore, when the Country initialization function is assigned capitalCity, it can also pass self as a parameter to the City initialization function. To sum up, you can create both Country and City instances in a statement without generating a strong reference ring, you can directly access capitalCity without using an exclamation point to expand its optional values:
Var country = Country (name: "Canada", capitalName: "Ottawa") println ("\ (country. name)'s captial city is called \ (country. capitalCity. name) ") // print" Canada's capital city is called Ottawa"
In the preceding example, the optional values expanded implicitly meet the requirements of the initialization functions of the two classes. After initialization, The capitalCity attribute can be used as a non-optional value type without generating a strongly referenced ring.
Strong reference loop of closure
Assign a closure to an attribute of the class instance, and the closure uses an instance, which also produces a strongly referenced ring. This closure may access a certain attribute of the instance, such as self. someProperty, or call a method of the instance, such as self. someMethod. Both cases lead to the use of self in the closure, thus generating a reference ring. A closure such as a class is a reference type, resulting in a strongly referenced ring. When you assign a closure to an attribute, you also assign a reference to the closure. Essentially, the problem described previously is the same-two strong references make each other valid all the time. However, unlike the two class instances, this time one is a class instance and the other is a closure. Swift provides an elegant way to solve this problem, which is called the closuer capture list ). The example below will show you how a strong reference loop is generated when a closure references self.
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 { println("\(name) is being deinitialized") } }
HTMLElement defines a name attribute to indicate the name of this element, such as "p" for a section, "br" for a line break, and an optional attribute text, used to set the text of HTML elements. In addition to the above two attributes, HTMLElement also defines a lazy attribute asHTML. This attribute references a closure that combines name and text into HTML string fragments. This attribute is of the ()-> String type, that is, "No parameter, return the String function ". By default, the closure is assigned to the asHTML attribute, which returns a string representing the HTML Tag. If the text value exists, the tag contains the optional text value or does not contain text. For a paragraph, the closure returns "<p> some text </p>" or "<p/>" based on whether the text is "some text" or nil ". You can name and use asHTML like the instance method. However, because asHTML is a closure instead of an instance method after all, if you change the HTML processing of a specific element, you can use a custom closure to replace the default value.
The closure uses self (referencing self. name and self. text), so the closure occupies self, which means that the closure in turn holds the strong reference of the HTMLElement instance. In this way, a strongly referenced ring is generated.
AvoidStrong reference loop generated by closures
When defining a closure, you can define a capture list as part of the closure to solve the strong reference ring between the closure and the class instance. The capture List defines the rules in the closure that possess one or more reference types. Similar to solving the strong reference ring between two class instances, it is declared that each occupied reference is a weak reference or a non-held reference, rather than a strong reference. Depending on the code relationship, determine whether to use weak references or not to hold references. Note: Swift has the following constraints: as long as you use the self member in the closure, you need to use self. someProperty or self. someMethod (not just someProperty or someMethod ). This can remind you that you may have self accidentally.
Define a capture list
Each element in the capture list is composed of the weak or unowned keyword and instance reference (such as self or someInstance. Each pair is in curly brackets separated by commas. The capture list is placed before the closure parameter list and return type:
lazy var someClosure: (Int, String) -> String = { [unowned self] (index: Int, stringToProcess: String) -> String in // closure body goes here }
If the closure does not specify the parameter list or return type (which can be inferred by context), the occupied list is placed at the beginning of the closure, followed by the keyword in:
lazy var someClosure: () -> String = { [unowned self] in // closure body goes here }
In the above HTMLElement example, a non-holding reference is a correct solution to strong reference. In this way, the HTMLElement class is encoded to avoid the strong reference ring:
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 { println("\(name) is being deinitialized") } }
The above HTMLElement implementation is the same as the previous implementation, but it only occupies a list. Here, the owner list is [unowned self], which indicates "using a non-primary reference rather than a strong reference to possess self ". As before, we can create and print the HTMLElement instance:
Var paragraph: HTMLElement? = HTMLElement (name: "p", text: "hello, world") println (paragraph !. AsTHML () // print "<p> hello, world </p>"
Reference relationships such
This time, the closure occupies self in the form of no primary reference and does not hold a strong reference to the HTMLElement instance. If paragraph is assigned as nil, The HTMLElement instance is destroyed and the message printed by deinitializer is displayed.
Paragraph = nil // print "p is being deinitialized"