【51CTO.com Quick Translation】 Introduction As a modern high-level programming language, Swift provides strong support for memory management needs such as allocation and release in your application. It uses a technology called Automatic Reference Counting (ARC). By studying this article, you will further improve your ARC programming level in Swift development through the following:
getting Started Open Xcode and click the command "File\New\Playground..." Then, select the iOS platform, name it "MemoryManagement", and then select the [Next] command. Finally, save the project to the destination you want to store it, then delete the boilerplate code and save the project. Next, add the following code to your project file:
This code defines a class User and creates an instance of it. The class has an attribute called name, and defines an init method (called just after memory allocation) and a deinit method (called just after memory deallocation). The print statement is used to output what you want to see in a timely manner. From the output, you'll notice that "User John is initialized\n" is displayed in the sidebar; this information is printed by the print statement in the initialization method init. However, you'll notice that the print statement in the deinit method is never called. This means that the object is never destructed. Of course, this also means that it is never released. This is because the scope in which it is initialized is never closed - the project itself never leaves this scope - so the object is not deleted from memory. Now, let's change the above initialization method to look like this:
This statement creates a scope around the initialization of the user1 object. So, after the scope ends, we want the user1 object to be released. Now, you can see that both print statements corresponding to the initializer and destroyer methods are output in the sidebar. This indicates that the object is destroyed after the scope defined above ends, just before it is deleted from memory. In summary, the life cycle of a Swift object consists of five stages: 1. Allocate (allocate memory from the stack or heap) 2. Initialization (init code runs) 3. Use (object of use) 4. Destruction (deinit code runs) 5. Release (memory is returned to the stack or heap) Although there is no direct hook technology to ambush memory allocation and memory recycling, you can use print statements as a proxy to monitor the above process in the init and deinit methods. Note that although the release and destruction methods in the above processes 4 and 5 are often used interchangeably, they are actually two different stages in the life cycle of an object. Reference counting is a mechanism for releasing an object when it is no longer needed. The question here is: "When can you be sure that you will not need this object in the future?" This management purpose is achieved by keeping a statistical count of the number of times it is used, namely the "reference count". The reference count is stored inside each object instance. The above count can determine how many "things" reference the object. When an object's reference count drops to zero, that is, the object's client no longer exists; then, the object is destroyed and the memory is deallocated; please refer to the following diagram for an example. When you initialize the User object, the object's reference count starts out as 1 because the constant user1 references the object. At the end of the do block, user1 goes out of scope, the count is decremented by 1, and the reference count is decremented to zero. As a result, user1 is deallocated and the memory is subsequently deallocated. Reference Cycles In most cases, ARC works like a charm. As a developer, you usually don't have to worry about memory leaks, such as whether unused objects are still alive in memory. But things are not always smooth sailing! Memory leaks can also happen! How does a leak occur? Let's imagine a situation where two objects are no longer needed, but each of them references the other. Since each object has a non-zero reference count; then, the release of these two objects will never occur. This is called a strong reference cycle. It fools ARC and prevents it from being cleaned up from memory. As you can see, the reference count at the end is not zero, so object1 and object2 are never released, even though they are no longer needed. To see a real example of this happening, add the following code after the definition of the User class and just before the existing do statement:
Then, modify the do block to look like this:
This will add a new class called Phone and create an instance of this new class. This new class is quite simple: it has two properties, one for model storage and one for owner, and an initialization method init and a destructor method deinit. Among them, the owner property is optional, because Phone can exist without User. Next, add the following code to the User class, just after the name property:
This code adds a phones array property to hold all the phone numbers owned by a user. Also, the setter method is private so that clients are forced to use the add(phone:) method. This method ensures that the owner is set correctly when you add a new number. Currently, as you can see in the sidebar, both the Phone and User objects are released as expected. But now, if you modify the do statement block to look like this:
Here, you add the iPhone to user1. This automatically sets the owner of the iPhone to user1. A strong reference cycle between these two objects prevents ARC from reallocating them. As a result, neither user1 nor the iPhone is ever released. Weak References To break reference cycles, you can specify the relationship between reference-counted objects as weak. Unless otherwise specified, all references are strong references. In contrast, weak references do not increase the strong reference count of an object. In other words, weak references do not participate in the life cycle management of the object. In addition, weak references are always declared as optional types. This means that when the reference count reaches zero, the reference can be automatically set to nil. In the above diagram, the dotted arrows represent weak references. Note that the reference count of object1 in the diagram is 1 because variable variable1 references it. The reference count of Object2 is 2 because both variable2 and object1 reference it. However, object2 weakly references object1, which means it does not affect the strong reference count of object1. When both variables (variable1 and variable2) are destroyed, the reference count of object1 is zero and deinit is called. This removes the strong reference to object2; of course, object2 is then also destroyed. Now, please open the sample project above again and break the reference cycle between User and Phone by making owner a weak reference. The code is as follows:
The corresponding diagram is as follows: Now, both user1 and iphone variables are properly freed at the end of the do block. You can see this in the output in the sidebar. Unowned References The Swift language also introduces another reference modifier that does not increase the reference count: unowned. So, what is the difference between unowned and weak references? Weak references are always optional and automatically become nil when the referenced object is destroyed. This is why you must define weak properties as optional var type in order for your code to compile (because the variable needs to be changed). Unowned references, by contrast, are by no means optional. If you try to access an unowned property that refers to a destructed object, you will trigger a runtime error, as shown below. Next, let's actually use the unowned modifier. Add a new class CarrierSubscription before the do block above, as shown below:
CarrierSubscription has four properties: subscription name, country code, phone number and a reference to a User object. Next, add the following statement to the User class, just after the definition of the name property: var subscriptions: [CarrierSubscription] = [] This will add a subscriptions property that stores an array of CarrierSubscription objects. Additionally, add the following code to the top of the Phone class, just after the owner property:
This will add an optional CarrierSubscription property and two new functions. Next, add the following code to the init method of the CarrierSubscription class, just before the print statement: user.subscriptions.append(self) This will ensure that the CarrierSubscription is added to the user's subscriptions array. Finally, modify the do block as follows:
Please pay attention to the results printed in the sidebar. Again, you can see a reference cycle: user1, iPhone or subscription1 are not released at the end. Can you find where the problem is? Both the reference from user1 to subscription1 and the reference from subscription1 to user1 should be unowned references, thus breaking the cycle. Now the question is: which of these two should we choose? To solve this problem, you need a little knowledge about domains to help. A User owns a Subscription, but a Subscription does not own a User. Furthermore, a CarrierSubscription does not have any meaning without a User that owns it. That's why you declare it as an immutable let property in the first place. Since a user can exist without a CarrierSubscription, but a CarrierSubscription does not have to exist without a user; therefore, the user reference should be unowned. Next, add the unowned modifier to the CarrierSubscription user property, like this:
This way, the reference cycle is broken, allowing each object to release its memory allocation. Reference cycle problem of closure Object reference cycles occur when properties refer to each other. Like objects, closures are reference types and can therefore also cause reference cycles. However, closures capture the objects they operate on. For example, if a closure is assigned to a class attribute, and the closure uses an instance attribute of the same class, a reference cycle occurs. In other words, the object has a reference to the closure through the saved attribute; and the closure also holds a reference to the object through the self keyword. Please refer to the following figure for further understanding. Add the following code to the CarrierSubscription definition, just after the definition of the user property:
This closure computes and returns a complete phone number. Note that this property is declared using the lazy keyword; this means that it is not assigned until the first time it is used. This is necessary because it uses self.countryCode and self.number; these are not available until after the initialization runs. Now, add the following line of code to the end of the do block:
From the above output, you can see that both user1 and iPhone objects can successfully reclaim memory allocations, but CarrierSubscription cannot. This is due to the strong reference cycle between the objects and the closure. Swift provides a simple and elegant way to break closures in strong reference cycles. The method is: we just declare a capture list and define the relationship between the closure and the objects it captures in this list. To illustrate how capture lists work, consider the following code:
In the code above, variable x is in the capture list; therefore, a copy of x is created at the point of definition of the closure. This is called capturing by value. On the other hand, y is not defined in the capture list, so it is captured by reference. This means that when the closure runs, the value of y will correspond to any possible value at that time, rather than the original value at the point of capture. Therefore, capture lists are used to define relationships between weak or unowned references inside a closure. In the above example, unowned references are a good choice because the closure cannot exist after the CarrierSubscription instance disappears. Now, change the completePhoneNumber closure of CarrierSubscription to look like this:
This code will add [unowned self] to the capture list of the closure. This means that self is captured as an unowned reference instead of a strong reference. This technique completely solves the reference cycle problem! The syntax used here is actually a shorthand for a longer capture syntax that introduces a new identifier. Consider the following longer form:
Here, newID is an unowned copy of self. Outside the closure scope, self retains its original meaning. As you used the short form above, a new self variable is created—this variable simply “shadows” the existing self variable in the closure scope. In your code, the relationship between self and the closure completePhoneNumber should be an unowned reference. If you are sure that a referenced object in the closure will never be released, then you can use an unowned reference. If the object is sure to release the memory, then there is trouble. Please add the following code to the end of the above sample project file:
A runtime exception will be raised when the program is run, because the closure expects self.who to still be valid, but it is released when the mermaid variable goes out of its scope. This example may seem a bit contrived, but this can easily happen in real development - for example, when you use a closure to run something much later (such as after an asynchronous network call completes). OK, now please change the greetingMaker variable in WWDCGreeting to the following:
In this code, you make two changes to the original greetingMaker. First, you use weak instead of unowned. Second, since self becomes a weak type, you need to use self?.who to access the who property. When you run the example project again, the system no longer crashes, but you get a strange output in the sidebar: "Hello, nil.". Maybe this is acceptable, but more often than not you want to do something completely different when the object is gone forever. Swift's guard let statement makes it very easy to do this. Let's rewrite our closure one last time so that it looks like this:
The guard statement binds a new variable, strongSelf, from the weak welf. If self is nil, the closure will return "No greeting available." On the other hand, if self is not nil, strongSelf will hold a strong reference; this way, the object is guaranteed to remain valid until the end of the closure. This term, sometimes called the strong-weak dance, is a powerful pattern for handling this behavior in closures in Swift. A complete example of a reference cycle Now that you understand the principles of ARC in Swift, and you understand what reference cycles are and how to break them, let’s look at a real-world example. First, download one of the starter projects I provided and open it in Xcode 8 (or later), as Xcode 8 adds some interesting new features that you’ll want to use. After that, build and run the project and you should see the following: This is a simple contacts app. Feel free to tap on a contact to get more information, or use the + button in the upper right corner to add a contact. Now, let's outline what the key codes do:
However, there is something terribly wrong with this project: there is a reference cycle in the code! For quite some time, your users won’t notice this because the leaked objects in this problem are small — their size makes it harder to track down. Fortunately, Xcode 8 provides a new built-in tool to help you find even the smallest leaks. Build and run the app again. Try deleting three or four contacts. It looks like they're completely gone, right? While the app is still running, move to the bottom of Xcode and click the Debug Memory Graph button: Please observe the new problem type introduced in Xcode 8: Runtime Issues. They look like a white exclamation mark icon in a purple box, please refer to the selected part shown in the screenshot below: In the Navigator, select one of the problematic Contact objects. The circular reference is now clearly visible: the Contact and Number objects keep each other alive - by referencing each other. See the following image: This type of diagram provides a visual indicator of where you can find errors in your code. Consider this: a contact can exist without a number, but a number should not exist without a contact. So how would you resolve this circular problem? Readers are strongly advised to try to solve this problem by themselves first, and then refer to the solution below. Actually, there are two possible solutions: you can make the relationship from Contact to Number a weak reference type, or you can make the relationship from Number to Contact an unowned type. Both solutions can effectively solve the circular reference problem. 【Note】 Apple's official documentation (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html) recommends that a parent object should strongly reference a child object. This means that Number should be strongly referenced in Contact, leaving Number with no owner to reference Contact. Please refer to the code answer below:
Circular references and value types and reference types Swift types are divided into reference types (such as classes) and value types (such as structures or enumerations). The main difference is that value types are copied when passed around, while reference types share a single copy of the reference information. Does this mean that there are no cycles when using value types? Yes: if everything is copied using value types, there will be no circular reference relationships, because no real references are created. You need at least two references to form a cycle, right? Let's go back to the previous project code and add the following content at the end:
Run it and you'll notice that the compiler won't compile properly. The reason is that a structure (value type) cannot be recursive or use an instance of itself; otherwise, this type of structure would have infinite size. Now, let's change it to a class like this:
Self-reference is not a problem for classes (i.e. reference types), so the compiler error goes away. Now, add the following code to your above file:
The example here provides an example of a reference cycle formed by mixing value types and reference types. Ernie and Bert survive normally - by keeping references to each other in their friends array, even though the array itself is a value type. If you change the array to unowned type, Xcode will display an error: unowned only applies to class types. To break the cycle here, you have to create a generic wrapper object and use it to add instances to the array. If you don't know what generics are or how to use them, check out the official website's tutorial on generics. OK, now please add the following code above the definition of the Person class:
Then, change the definition of the friends attribute in Person to look like this:
Finally, modify the do block to look like this:
Now, both Ernie and Bert can be released normally! Here, the friends array is no longer a collection of Person objects, but becomes a collection of unowned objects - objects that act as wrappers around Person instances. To access the Person object from within the Unowned object, we can use the value property, like this:
summary The complete sample project download address is https://koenig-media.raywenderlich.com/uploads/2016/08/MemoryManagement.playground.zip. By the end of this article, you should have a good understanding of Swift's memory management and know how ARC works. If you want a deeper dive into how Swift implements weak references, check out Mike’s blog post “Weak References in Swift”. [Translated by 51CTO. Please indicate the original translator and source as 51CTO.com when reprinting on partner sites] |
<<: Hidden career crisis in the technological era
>>: VR Oscars: Proto Awards announces 2016 winning apps - who are the big winners?
It is reported that the official account of SAIC ...
Recently, the China Automobile Dealers Associatio...
There is another big pitfall in App soft promotio...
How much does it cost to attract investment throu...
At the early morning of November 21, Beijing time...
Nobel Prize dinner cancelled According to the CCT...
I had the same experience. I was tricked by the s...
In most cases, many people are easily influenced ...
Some people regard cars as a means of transportat...
With the implementation of the "Double Point...
Li ChuanfuHuang Ting In the vast field of biology...
This article mainly discusses the response strate...
Regarding event planning, I have written an artic...
At the end of September last year, while releasing...
With the decline of old mobile phone giants, Sams...