Swift 4.0: Automatic Reference Counting (ARC) — Part 2
Welcome back to Part Two of this tutorial on Automatic Reference Counting! In the First Part, you learned about some fundamental concepts of ARC such as weak
, Unowned
and how to resolve Reference Cycle using weak
and unowned
.
In this final part, you’ll learn about one last scenario, where both the properties
should have a value and neither property should ever be nil
once the initialization is complete. Also, you would learn how to overcome the Strong Reference Cycle in Closures
. Let’s dive straight into it.
Suppose we have a Country
and President
class. Each of these class stores an instance of the other class as a variable. This means that every Country should have a President and every President should be associated with a Country.
To fulfil this requirement without causing a memory Leak, you would need to declare one property (In our case countryPresident
in the Country
Class) as an implicitly unwrapped optional Property. This can be done by placing the exclamation mark at the end of its type annotation(President!
). While for the other, you would need to declare it as an unowned property (In our case country
in the President
Class).
class Country {
let countryName: String
var countryPresident: President! init(countryName: String, presidentName: String) {
self.countryName = countryName
self.countryPresident = President(presidentName: presidentName, country: self)
print("Country is being initialised")
} deinit {
print("Country is being de-initialised")
}
}class President {
let presidentName: String
unowned let country: Country
init(presidentName: String, country: Country) {
self.presidentName = presidentName
self.country = country
print("President is being initialised")
} deinit {
print("President is being de-initialised")
}
}
Because countryPresident
has a default nil
value, a new Country instance is considered fully initialized as soon as the Country instance sets its countryName
property within its initializer. This means that the Country initializer can start to reference and pass around the implicit self
property as soon as the countryName
property is set. The Country initializer can therefore pass self
as one of the parameters for the President initializer when the Country initializer is setting its own countryPresident property. Doing so doesn't create any strong Reference (Image 1–1).
var country:Country? = Country(countryName: "India", presidentName: "Ram Nath Kovind")// Prints "President is being initialised"
// Prints "Country is being initialised"
Now, when the country is set to nil
, president would also be automatically nullified (Image 1-2).
country = nil// Prints "President is being de-initialised"
// Prints "Country is being de-initialised"
Try removing unowned
from the President Class’s country
property. Observe the leak caused when setting country
as nil
.
Strong Reference Cycle in Closures
A strong reference cycle can also occur if you assign a closure to a property of a class instance and the body of that closure captures the instance. This capture might occur because the closure’s body accesses a property of the instance, such as self.someProperty
, or because the closure called a method on the instance, such as self.someMethod()
. In either of the case, these access would cause the closure to “capture” self
, creating a strong reference cycle.
This strong reference cycle occurs because closures, like classes, are reference types.
Lets see how this strong reference cycle is caused. Suppose you have a Person Class with firstName
, lastName
property. You also have a lazy
closure property which would return you a full name by combining firstName
and lastName
. Lets name it as fullName
of type()->String
To know more about closure, you can read the Apple Documentation.
The Person Class provides a single initializer, which takes a 2 argument (firstName
and lastName
). The class also defines a deinitializer, which prints a message to show when an Person instance is deallocated.
class Person {
var firstName: String?
var lastName: String? lazy var fullName: ()->String = {
return ("\(self.firstName!) \(self.lastName!)")
} init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
print("Person Class is being initialised")
} deinit {
print("Person Class is being de-initialised")
}
}
The person variable above is defined as an optional Person, so that it can be set to
nil
to demonstrate the presence of a strong reference cycle.
The Person instance’s fullName
property holds a strong reference to its closure. Also, because the closure refers to self
within its body (as a way to reference self.firstName
and self.lastName
), the closure captures self
. This means that it also holds a strong reference to the Person instance. As a result, strong reference cycle is created between the two (Image 2–1).
Even though the closure refers to
self
multiple times, Swift ensures that it only captures one strong reference to the Person instance.
var person: Person? = Person(firstName: "Rahul", lastName: "Singh")
// Prints "Person Class is being initialised"print(person!.fullName())
// Prints "Rahul Singh"
No message is logged while setting the person variable to nil
. This shows that neither the Person instance nor its closure were deallocated. This is because of the Strong Reference Cycle created between the variable and closure (Image 2–2).
person = nil
How to resolve the Strong Reference between Class and Closure?
To solve this problem, Swift has an elegent way called as Closure Capture List. You can define the capture list as a part of closure’s definition. A capture list defines a rule to use when capturing one or more reference types within the closure’s body. The way strong reference cycles between two class instances is resolved, ie, you declare each captured reference either to be a weak
or unowned
, likewise you can choose theses referneces depending on the relationships between the different parts of your code.
Each item in a capture list is a pairing of the
weak
orunowned
keyword with a reference to a class instance (such asself
) or a variable initialized with some value (such asdelegate
=self.delegate!
). These pairings are written within a pair of square braces, separated by commas.
The implementation of Person Class remains same to the previous implementation, apart from the addition of a capture list within the fullName
closure (Image 2–3). The capture list is [unowned self]
, which means “capture self as an unowned reference rather than a strong reference”.
class Person {
var firstName: String?
var lastName: String?
lazy var fullName: ()-> String = {[unowned self] in
return ("\(self.firstName!) \(self.lastName!)")
} init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
print("Person Class is being initialised")
} deinit {
print("Person Class is being de-initialised")
}
}var person: Person? = Person(firstName: "Rahul", lastName: "Singh")
// Prints "Person Class is being initialised"print(person!.fullName())
// Prints "Rahul Singh"
In contrast to earlier, this time setting person variable to nil
would deallocate the Person Instance and print the desired message (Image 3–4).
person = nil
// Prints "Person Class is being de-initialised"
Source: Apple Docs and of course Internet 😄
The Playground file can be found here. Feel free to play around with it. Now, poke in if we can use weak
instead of unowned
in the Closure of the Person Class? If yes, how can this be achieved?
Hope you have liked my tutorial on Automatic Reference Counting.
Do write your comments, if you enjoyed this post. I’d like to grow my readership. Can you help me out by sharing this blog post?