Swift: ARC and memory management
- Transfer
- Tutorial
Being a modern high-level language, Swift basically takes care of the memory management in your applications, allocating and freeing memory. This is due to a mechanism called Automatic Reference Counting , or ARC for short . In this guide, you will learn how ARC works and how to properly manage memory in Swift. Understanding this mechanism, you can influence the lifetime of objects located on the heap ( heap ).
In this guide, you will build on your knowledge of Swift and ARC by learning the following:
Download the source materials. Open the project in the Cycles / Starter folder . In the first part of our guide, understanding key concepts, we will deal exclusively with the MainViewController.swif t file .
Add this class at the bottom of MainViewController.swift:
The User class is defined here , which, with the help of print statements, signals us about the initialization and release of the class instance.
Now create an instance of the User class at the top of the MainViewController.
Place this code before the viewDidLoad () method :
Launch the app. Make the Xcode console visible with Command-Shift-Y to see the output of the print statements.
Notice that User John was initialized appeared on the console , but the print statement inside deinit was not executed. This means that this object was not released, since it did not go out of scope .
In other words, until the view controller containing this object goes out of scope, the object will never be freed.
By wrapping an instance of the User class in a method, we will allow it to go out of scope, thereby allowing ARC to free it.
Let's create the runScenario () method inside the MainViewController class and move the initialization of the User class instance inside it.
runScenario () defines the scope of the User instance. When exiting this zone, user must be freed.
Now call runScenario () adding this at the end of viewDidLoad ():
Launch the app. The output in the console now looks like this:
User John was initialized
Deallocating user named: John
This means that you released the object that left the scope.
The existence of the object is divided into five stages:
There is no direct way to track the steps of allocating and freeing memory, but you can use the code inside init and deinit.
Reference count ( the reference counts ), also known as the 'number of uses' ( usage counts ), determines when an object is no longer needed. This counter shows the number of those who "use" this object. An object becomes unnecessary when the usage counter is zero. Then the object is de-initialized and released.
When the User object is initialized, its reference count is 1, because the user constant refers to this object.
At the end of runScenario (), user goes out of scope and the reference count is reduced to 0. As a result, user is uninitialized and then freed.
In most cases, ARC works as it should. The developer usually does not need to worry about memory leaks when unused objects remain unallocated indefinitely.
But not always! Possible memory leaks.
How can this happen? Imagine a situation where two objects are no longer used, but each of them refers to the other. Since each reference count is not 0, none of them will be freed.
This is a strong reference cycle . This situation confuses the ARC and does not allow it to clear the memory.
As you can see, the reference count at the end is not 0, and although no objects are needed anymore, object1 and object2 will not be freed.
To test all this in action, add this code after the User class in MainViewController.swift:
This code adds a new Phone class with two properties, one for the model and one for the owner, as well as the init and deinit methods. The owner’s property is optional, since the phone may not have an owner.
Now add this line to runScenario ():
This will create an instance of the Phone class.
Now add this code to the User class, immediately after the name property:
Add an array of phones owned by user. The setter is marked as private, so add (phone :) must be used.
Launch the app. As you can see, instances of the Phone and User objects classes are released as needed.
User John was initialized
Phone iPhone XS was initialized
Deallocating phone named: iPhone Xs
Deallocating user named: John
Now add this to the end of runScenario ():
Here we add our iPhone to the list of phones owned by user , and also set the owner property of the phone to ' user '.
Run the application again. You will see that user and iPhone objects are not released. The cycle of strong links between them prevents the ARC from releasing them.
To break the cycle of strong references, you can mark the relationship between objects as the weak ( weak ).
By default, all links are strong and assignment leads to an increase in the reference count. When using weak references, the reference count does not increase.
In other words, weak links do not affect the life management of an object . Weak links are always declared optional . That way, when the link count becomes 0, the link can be set to nil.
In this illustration, dashed lines indicate weak links. Note that the reference count of object1 is 1, since variable1 refers to it. The reference count of object2 is 2, because it is referenced by variable2 and object1.
object2 also references object1, but WEAK , which means that it does not affect the reference count of object1.
When variable1 and variable2 are freed, object1 has a reference count of 0, which frees it. This, in turn, releases a strong reference to object2, which already leads to its release.
In the Phone class, change the owner property declaration as follows:
By declaring the owner property reference as 'weak', we break the loop of strong links between the User and Phone classes.
Launch the app. Now user and phone are correctly released.
There is also another link modifier that does not increase the reference count: unowned .
What is the difference between unowned and weak ? A weak reference is always optional and automatically becomes nil when the referenced object is released.
This is why we should declare weak properties as an optional variable of type: this property must change.
Unowned links, in contrast, are never optional. If you try to access an unowned property that refers to a freed object, you will get an error that looks like a force unwrap containing a nil variable (force unwrapping).
Let's try apply unowned .
Add a new classCarrierSubscription at the end of MainViewController.swift:
CarrierSubscription has four properties:
Name: provider name.
CountryCode: country code.
Number: phone number.
User: link to the user.
Now add this to the User class after the name property:
Here we keep an array of user providers.
Now add this to the Phone class, after the owner property:
This adds the optional CarrierSubscription property and two methods for registering and unregistering the phone with the provider.
Now add the CarrierSubscription class inside the init method, right before the print statement:
We add CarrierSubscription to the array of user providers.
Finally, add this at the end of the runScenario () method:
We create a subscription to the provider for the user and connect the phone to it.
Launch the app. In the console you will see:
User John was initialized
Phone iPhone Xs was initialized
CarrierSubscription TelBel is initialized
And again a link cycle! user, iPhone and subscription did not free at the end.
Can you find a problem?
Either the link from user to subscription or the link from subscription to user must be unowned to break the loop. The question is which option to choose. Let's look at the structures.
A user owns a subscription to a provider, but vice versa - no, a subscription to a provider does not own a user.
Moreover, there is no point in the existence of CarrierSubscription without reference to the user who owns it.
Therefore, the user link must be unowned.
Change the user declaration in CarrierSubscription:
Now user unowned, which breaks the loop of links and allows you to free all objects.
Link cycles for objects occur when objects have properties that reference each other. Like objects, closures are a reference type, and can lead to reference loops. Closures capture objects that they use.
For example, if you assign a closure to a property of a class, and this closure uses properties of the same class, then we get a loop of links. In other words, the object holds a link to the closure through the property. The closure contains a reference to the object through the captured value of self.
Add this code to CarrierSubscription immediately after the user property:
This closure calculates and returns the full telephone number. The property is declared as lazy , it will be assigned upon first use.
This is necessary because it uses self.countryCode and self.number, which will not be available until the initializer code is executed.
Add runScenario () to the end:
Calling completePhoneNumber () will execute the closure.
Launch the application and you will see that user and iPhone are released, but CarrierSubscription is not, due to a cycle of strong links between the object and the closure.
Swift provides a simple and elegant way to break the loop of strong links in closures. You declare a capture list in which you define the relationship between the closure and the objects that it captures.
To demonstrate the capture list, consider the following code:
x is in the closure capture list, so the value of x is copied to the closure definition. It is captured by value.
y is not in the capture list, it is captured by reference. This means that the value of y will be what it was at the time the circuit was called.
Lock lists help identify weak or unowned interactions with respect to objects captured within the loop. In our case, the appropriate choice is unowned, since a closure cannot exist if the CarrierSubscription instance is released.
Replace the completePhoneNumber definition with CarrierSubscription ::
We add [unowned self] to the closure capture list. This means that we captured self as an unowned link instead of a strong one.
Launch the application and you will see that CarrierSubscription is now released.
In fact, the above syntax is a short form of a longer and more complete one in which a new variable appears:
Here newID is an unowned copy of self. Beyond the closure, self remains itself. In the short form given earlier, we create a new self variable that obscures the existing self inside the closure.
In your code, the relationship between self and completePhoneNumber is designated as unowned.
If you are sure that the object used in the closure will not be released, you can use unowned. If he does, you are in trouble!
Add this code at the end of MainViewController.swift:
Now here is the end of runScenario ():
Launch the application and you will see a crash and something in the console:
User John was initialized
Phone iPhone XS was initialized
CarrierSubscription TelBel is initialized
0032 31415926
Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated2019-02-24 12: 29: 40.744248-0600 Cycles [33489: 5926466] Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated
An exception occurred because the closure waits for self.who to exist, but it was released as soon as mermaid out of scope at the end of the do block.
This example may look sucked from a finger, but such things happen. For example, when we use closures to start something much later, say, after the asynchronous call on the network has ended.
Replace greetingMaker in the WWDCGreeting class with this:
We did two things: first, we replaced unowned with weak. Secondly, since self has become weak, we access the who property through self? .Who. Ignore the Xcode warning, we will fix it soon.
The application no longer crashes, but if you run it, we get a funny result: “Hello nil.”
Perhaps the result is quite acceptable, but often we need to do something if the object was freed. This can be done using the guard statement.
Replace the closure text with this:
The guard statement assigns self taken from weak self. If self is nil, the closure returns “No greeting available.” Otherwise, self becomes a strong reference, so the object is guaranteed to live to the end of the closure.
Now that you understand how ARC works, what are link loops and how to break them, it's time to see an example of a real application.
Open the Starter project located in the Contacts folder.
Launch the app.
This is the simplest contact manager. Try clicking on a contact, add a couple of new ones.
File Assignment:
ContactsTableViewController: shows all contacts.
DetailViewController: shows the detailed information of the selected contact.
NewContactViewController: allows you to add a new contact.
ContactTableViewCell: table cell showing contact details.
Contact: contact model.
Number: phone number model.
However, with this project, everything is bad: there is a cycle of links. At first, users will not notice problems due to the small size of the leaking memory, for the same reason it is difficult to find the leak.
Fortunately, Xcode 10 has built-in tools to find the smallest memory leak.
Launch the application again. Delete 3-4 contacts using the swipe to the left and the delete button. It seems like they disappear completely, right?
With the application running, click on the Debug Memory Graph button:
Observe the Runtime Issues in the Debug navigator. They are marked with purple squares with a white exclamation mark inside:
Select one of the problematic Contact objects in the navigator. The cycle is clearly visible: the Contact and Number objects, referring to each other, hold.
Looks like you should look into the code. Keep in mind that a contact can exist without a number, but not vice versa.
How would you resolve this loop? Link from Contact to Number or from Number to Contact? weak or unowned? Try it yourself first!
Swift has reference types (classes and closures) and value types (structures, enumerations). The value type is copied when it is passed, and reference types share the same value using the link.
This means that in the case of value types, there can be no cycles. For a loop to occur, we need at least 2 reference types.
Let's go back to the Cycles project and add this code at the end of MainViewController.swift:
Will not work! Structure is a value type and cannot have recursion on an instance of itself. Otherwise, such a structure would have infinite size.
Change the structure to a class.
The reference to itself is quite acceptable for classes (reference type), so the compiler does not have problems.
Now add this at the end of MainViewController.swift:
And this is at the end of runScenario ():
Launch the app. Please note: neither ernie nor bert are released.
This is an example of a combination of a reference type and a value type that has led to a loop of links.
ernie and bert remain unreleased, holding each other in their friends arrays, although the arrays themselves are value types.
Try to make the friends archive as unowned, and Xcode will show an error: unowned applies only to classes.
To fix this loop, we have to create a wrapper object and use it to add instances to the array.
Add the following definition before the Person class:
Then change the definition of friends in the Person class:
Finally, replace the contents of the do block in runScenario ():
Launch the application, now ernie and bert are correctly released!
The friends array is no longer a collection of Person objects. This is now a collection of Unowned objects that serve as wrappers for Person instances.
To get Person objects from Unowned, use the value property:
You now have a good understanding of memory management in Swift and you know how ARC works. I hope the publication was useful to you.
Apple: Automatic Reference Counting
In this guide, you will build on your knowledge of Swift and ARC by learning the following:
- how ARC works
- what are reference cycles and how to fix them correctly
- how to create an example link loop
- How to find link loops using visual tools offered by Xcode
- how to handle reference types and value types
Getting started
Download the source materials. Open the project in the Cycles / Starter folder . In the first part of our guide, understanding key concepts, we will deal exclusively with the MainViewController.swif t file .
Add this class at the bottom of MainViewController.swift:
class User {
let name: String
init(name: String) {
self.name = name
print("User \(name) was initialized")
}
deinit {
print("Deallocating user named: \(name)")
}
}
The User class is defined here , which, with the help of print statements, signals us about the initialization and release of the class instance.
Now create an instance of the User class at the top of the MainViewController.
Place this code before the viewDidLoad () method :
let user = User(name: "John")
Launch the app. Make the Xcode console visible with Command-Shift-Y to see the output of the print statements.
Notice that User John was initialized appeared on the console , but the print statement inside deinit was not executed. This means that this object was not released, since it did not go out of scope .
In other words, until the view controller containing this object goes out of scope, the object will never be freed.
Is he in scope?
By wrapping an instance of the User class in a method, we will allow it to go out of scope, thereby allowing ARC to free it.
Let's create the runScenario () method inside the MainViewController class and move the initialization of the User class instance inside it.
func runScenario() {
let user = User(name: "John")
}
runScenario () defines the scope of the User instance. When exiting this zone, user must be freed.
Now call runScenario () adding this at the end of viewDidLoad ():
runScenario()
Launch the app. The output in the console now looks like this:
User John was initialized
Deallocating user named: John
This means that you released the object that left the scope.
Object lifetime
The existence of the object is divided into five stages:
- memory allocation: from the stack or from the heap
- initialization: code is executed inside init
- using
- deinitialization: code is executed inside deinit
- free memory: allocated memory is returned to the stack or heap
There is no direct way to track the steps of allocating and freeing memory, but you can use the code inside init and deinit.
Reference count ( the reference counts ), also known as the 'number of uses' ( usage counts ), determines when an object is no longer needed. This counter shows the number of those who "use" this object. An object becomes unnecessary when the usage counter is zero. Then the object is de-initialized and released.
When the User object is initialized, its reference count is 1, because the user constant refers to this object.
At the end of runScenario (), user goes out of scope and the reference count is reduced to 0. As a result, user is uninitialized and then freed.
Reference Cycles
In most cases, ARC works as it should. The developer usually does not need to worry about memory leaks when unused objects remain unallocated indefinitely.
But not always! Possible memory leaks.
How can this happen? Imagine a situation where two objects are no longer used, but each of them refers to the other. Since each reference count is not 0, none of them will be freed.
This is a strong reference cycle . This situation confuses the ARC and does not allow it to clear the memory.
As you can see, the reference count at the end is not 0, and although no objects are needed anymore, object1 and object2 will not be freed.
Check out our links
To test all this in action, add this code after the User class in MainViewController.swift:
class Phone {
let model: String
var owner: User?
init(model: String) {
self.model = model
print("Phone \(model) was initialized")
}
deinit {
print("Deallocating phone named: \(model)")
}
}
This code adds a new Phone class with two properties, one for the model and one for the owner, as well as the init and deinit methods. The owner’s property is optional, since the phone may not have an owner.
Now add this line to runScenario ():
let iPhone = Phone(model: "iPhone Xs")
This will create an instance of the Phone class.
Hold the mobile
Now add this code to the User class, immediately after the name property:
private(set) var phones: [Phone] = []
func add(phone: Phone) {
phones.append(phone)
phone.owner = self
}
Add an array of phones owned by user. The setter is marked as private, so add (phone :) must be used.
Launch the app. As you can see, instances of the Phone and User objects classes are released as needed.
User John was initialized
Phone iPhone XS was initialized
Deallocating phone named: iPhone Xs
Deallocating user named: John
Now add this to the end of runScenario ():
user.add(phone: iPhone)
Here we add our iPhone to the list of phones owned by user , and also set the owner property of the phone to ' user '.
Run the application again. You will see that user and iPhone objects are not released. The cycle of strong links between them prevents the ARC from releasing them.
Links Weak
To break the cycle of strong references, you can mark the relationship between objects as the weak ( weak ).
By default, all links are strong and assignment leads to an increase in the reference count. When using weak references, the reference count does not increase.
In other words, weak links do not affect the life management of an object . Weak links are always declared optional . That way, when the link count becomes 0, the link can be set to nil.
In this illustration, dashed lines indicate weak links. Note that the reference count of object1 is 1, since variable1 refers to it. The reference count of object2 is 2, because it is referenced by variable2 and object1.
object2 also references object1, but WEAK , which means that it does not affect the reference count of object1.
When variable1 and variable2 are freed, object1 has a reference count of 0, which frees it. This, in turn, releases a strong reference to object2, which already leads to its release.
In the Phone class, change the owner property declaration as follows:
weak var owner: User?
By declaring the owner property reference as 'weak', we break the loop of strong links between the User and Phone classes.
Launch the app. Now user and phone are correctly released.
Unowned Links
There is also another link modifier that does not increase the reference count: unowned .
What is the difference between unowned and weak ? A weak reference is always optional and automatically becomes nil when the referenced object is released.
This is why we should declare weak properties as an optional variable of type: this property must change.
Unowned links, in contrast, are never optional. If you try to access an unowned property that refers to a freed object, you will get an error that looks like a force unwrap containing a nil variable (force unwrapping).
Let's try apply unowned .
Add a new classCarrierSubscription at the end of MainViewController.swift:
class CarrierSubscription {
let name: String
let countryCode: String
let number: String
let user: User
init(name: String, countryCode: String, number: String, user: User) {
self.name = name
self.countryCode = countryCode
self.number = number
self.user = user
print("CarrierSubscription \(name) is initialized")
}
deinit {
print("Deallocating CarrierSubscription named: \(name)")
}
}
CarrierSubscription has four properties:
Name: provider name.
CountryCode: country code.
Number: phone number.
User: link to the user.
Who is your provider?
Now add this to the User class after the name property:
var subscriptions: [CarrierSubscription] = []
Here we keep an array of user providers.
Now add this to the Phone class, after the owner property:
var carrierSubscription: CarrierSubscription?
func provision(carrierSubscription: CarrierSubscription) {
self.carrierSubscription = carrierSubscription
}
func decommission() {
carrierSubscription = nil
}
This adds the optional CarrierSubscription property and two methods for registering and unregistering the phone with the provider.
Now add the CarrierSubscription class inside the init method, right before the print statement:
user.subscriptions.append(self)
We add CarrierSubscription to the array of user providers.
Finally, add this at the end of the runScenario () method:
let subscription = CarrierSubscription(
name: "TelBel",
countryCode: "0032",
number: "31415926",
user: user)
iPhone.provision(carrierSubscription: subscription)
We create a subscription to the provider for the user and connect the phone to it.
Launch the app. In the console you will see:
User John was initialized
Phone iPhone Xs was initialized
CarrierSubscription TelBel is initialized
And again a link cycle! user, iPhone and subscription did not free at the end.
Can you find a problem?
Breaking the chain
Either the link from user to subscription or the link from subscription to user must be unowned to break the loop. The question is which option to choose. Let's look at the structures.
A user owns a subscription to a provider, but vice versa - no, a subscription to a provider does not own a user.
Moreover, there is no point in the existence of CarrierSubscription without reference to the user who owns it.
Therefore, the user link must be unowned.
Change the user declaration in CarrierSubscription:
unowned let user: User
Now user unowned, which breaks the loop of links and allows you to free all objects.
Loop links in closures
Link cycles for objects occur when objects have properties that reference each other. Like objects, closures are a reference type, and can lead to reference loops. Closures capture objects that they use.
For example, if you assign a closure to a property of a class, and this closure uses properties of the same class, then we get a loop of links. In other words, the object holds a link to the closure through the property. The closure contains a reference to the object through the captured value of self.
Add this code to CarrierSubscription immediately after the user property:
lazy var completePhoneNumber: () -> String = {
self.countryCode + " " + self.number
}
This closure calculates and returns the full telephone number. The property is declared as lazy , it will be assigned upon first use.
This is necessary because it uses self.countryCode and self.number, which will not be available until the initializer code is executed.
Add runScenario () to the end:
print(subscription.completePhoneNumber())
Calling completePhoneNumber () will execute the closure.
Launch the application and you will see that user and iPhone are released, but CarrierSubscription is not, due to a cycle of strong links between the object and the closure.
Capture Lists
Swift provides a simple and elegant way to break the loop of strong links in closures. You declare a capture list in which you define the relationship between the closure and the objects that it captures.
To demonstrate the capture list, consider the following code:
var x = 5
var y = 5
let someClosure = { [x] in
print("\(x), \(y)")
}
x = 6
y = 6
someClosure() // Prints 5, 6
print("\(x), \(y)") // Prints 6, 6
x is in the closure capture list, so the value of x is copied to the closure definition. It is captured by value.
y is not in the capture list, it is captured by reference. This means that the value of y will be what it was at the time the circuit was called.
Lock lists help identify weak or unowned interactions with respect to objects captured within the loop. In our case, the appropriate choice is unowned, since a closure cannot exist if the CarrierSubscription instance is released.
Seize yourself
Replace the completePhoneNumber definition with CarrierSubscription ::
lazy var completePhoneNumber: () -> String = { [unowned self] in
return self.countryCode + " " + self.number
}
We add [unowned self] to the closure capture list. This means that we captured self as an unowned link instead of a strong one.
Launch the application and you will see that CarrierSubscription is now released.
In fact, the above syntax is a short form of a longer and more complete one in which a new variable appears:
var closure = { [unowned newID = self] in
// Use unowned newID here...
}
Here newID is an unowned copy of self. Beyond the closure, self remains itself. In the short form given earlier, we create a new self variable that obscures the existing self inside the closure.
Use Unowned carefully
In your code, the relationship between self and completePhoneNumber is designated as unowned.
If you are sure that the object used in the closure will not be released, you can use unowned. If he does, you are in trouble!
Add this code at the end of MainViewController.swift:
class WWDCGreeting {
let who: String
init(who: String) {
self.who = who
}
lazy var greetingMaker: () -> String = { [unowned self] in
return "Hello \(self.who)."
}
}
Now here is the end of runScenario ():
let greetingMaker: () -> String
do {
let mermaid = WWDCGreeting(who: "caffeinated mermaid")
greetingMaker = mermaid.greetingMaker
}
print(greetingMaker()) // ЛОВУШКА!
Launch the application and you will see a crash and something in the console:
User John was initialized
Phone iPhone XS was initialized
CarrierSubscription TelBel is initialized
0032 31415926
Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated2019-02-24 12: 29: 40.744248-0600 Cycles [33489: 5926466] Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated
An exception occurred because the closure waits for self.who to exist, but it was released as soon as mermaid out of scope at the end of the do block.
This example may look sucked from a finger, but such things happen. For example, when we use closures to start something much later, say, after the asynchronous call on the network has ended.
Defuse the trap
Replace greetingMaker in the WWDCGreeting class with this:
lazy var greetingMaker: () -> String = { [weak self] in
return "Hello \(self?.who)."
}
We did two things: first, we replaced unowned with weak. Secondly, since self has become weak, we access the who property through self? .Who. Ignore the Xcode warning, we will fix it soon.
The application no longer crashes, but if you run it, we get a funny result: “Hello nil.”
Perhaps the result is quite acceptable, but often we need to do something if the object was freed. This can be done using the guard statement.
Replace the closure text with this:
lazy var greetingMaker: () -> String = { [weak self] in
guard let self = self else {
return "No greeting available."
}
return "Hello \(self.who)."
}
The guard statement assigns self taken from weak self. If self is nil, the closure returns “No greeting available.” Otherwise, self becomes a strong reference, so the object is guaranteed to live to the end of the closure.
Looking for link loops in Xcode 10
Now that you understand how ARC works, what are link loops and how to break them, it's time to see an example of a real application.
Open the Starter project located in the Contacts folder.
Launch the app.
This is the simplest contact manager. Try clicking on a contact, add a couple of new ones.
File Assignment:
ContactsTableViewController: shows all contacts.
DetailViewController: shows the detailed information of the selected contact.
NewContactViewController: allows you to add a new contact.
ContactTableViewCell: table cell showing contact details.
Contact: contact model.
Number: phone number model.
However, with this project, everything is bad: there is a cycle of links. At first, users will not notice problems due to the small size of the leaking memory, for the same reason it is difficult to find the leak.
Fortunately, Xcode 10 has built-in tools to find the smallest memory leak.
Launch the application again. Delete 3-4 contacts using the swipe to the left and the delete button. It seems like they disappear completely, right?
Where does it flow?
With the application running, click on the Debug Memory Graph button:
Observe the Runtime Issues in the Debug navigator. They are marked with purple squares with a white exclamation mark inside:
Select one of the problematic Contact objects in the navigator. The cycle is clearly visible: the Contact and Number objects, referring to each other, hold.
Looks like you should look into the code. Keep in mind that a contact can exist without a number, but not vice versa.
How would you resolve this loop? Link from Contact to Number or from Number to Contact? weak or unowned? Try it yourself first!
If you needed help ...
There are 2 possible solutions: either make a link from Contact to Number weak, or from Number to Contact unowned.
Apple's documentation recommends that the parent object has a strong reference to "child" - not vice versa. This means that we give Contact a strong reference to Number, and Number - an unowned link to Contact:
Apple's documentation recommends that the parent object has a strong reference to "child" - not vice versa. This means that we give Contact a strong reference to Number, and Number - an unowned link to Contact:
class Number {
unowned var contact: Contact
// Other code...
}
class Contact {
var number: Number?
// Other code...
}
Bonus: loops with reference types and value types.
Swift has reference types (classes and closures) and value types (structures, enumerations). The value type is copied when it is passed, and reference types share the same value using the link.
This means that in the case of value types, there can be no cycles. For a loop to occur, we need at least 2 reference types.
Let's go back to the Cycles project and add this code at the end of MainViewController.swift:
struct Node { // Error
var payload = 0
var next: Node?
}
Will not work! Structure is a value type and cannot have recursion on an instance of itself. Otherwise, such a structure would have infinite size.
Change the structure to a class.
class Node {
var payload = 0
var next: Node?
}
The reference to itself is quite acceptable for classes (reference type), so the compiler does not have problems.
Now add this at the end of MainViewController.swift:
class Person {
var name: String
var friends: [Person] = []
init(name: String) {
self.name = name
print("New person instance: \(name)")
}
deinit {
print("Person instance \(name) is being deallocated")
}
}
And this is at the end of runScenario ():
do {
let ernie = Person(name: "Ernie")
let bert = Person(name: "Bert")
ernie.friends.append(bert) // Not deallocated
bert.friends.append(ernie) // Not deallocated
}
Launch the app. Please note: neither ernie nor bert are released.
Link and meaning
This is an example of a combination of a reference type and a value type that has led to a loop of links.
ernie and bert remain unreleased, holding each other in their friends arrays, although the arrays themselves are value types.
Try to make the friends archive as unowned, and Xcode will show an error: unowned applies only to classes.
To fix this loop, we have to create a wrapper object and use it to add instances to the array.
Add the following definition before the Person class:
class Unowned {
unowned var value: T
init (_ value: T) {
self.value = value
}
}
Then change the definition of friends in the Person class:
var friends: [Unowned] = []
Finally, replace the contents of the do block in runScenario ():
do {
let ernie = Person(name: "Ernie")
let bert = Person(name: "Bert")
ernie.friends.append(Unowned(bert))
bert.friends.append(Unowned(ernie))
}
Launch the application, now ernie and bert are correctly released!
The friends array is no longer a collection of Person objects. This is now a collection of Unowned objects that serve as wrappers for Person instances.
To get Person objects from Unowned, use the value property:
let firstFriend = bert.friends.first?.value // get ernie
Conclusion
You now have a good understanding of memory management in Swift and you know how ARC works. I hope the publication was useful to you.
Apple: Automatic Reference Counting