Working with memory management and ARC
If you are coming from the old school where MRC (Manual Reference Counting) was being used for memory management, you definitely know how much headache developers suffer to manage memory in iOS. With iOS 5, Apple introduced ARC (Automatic Reference Counting), and life became easier in terms of memory management. Though ARC manages your memory automatically, some mistakes may ruin your memory with no mercy if you didn't understand the concept of memory management.
Getting ready
Before checking how to manage memory and avoid some common mistakes, I would like to highlight some notes:
Assigning a class instance to variable, constant, or properties will create a strong reference to this instance. The instance will be kept in memory as long as you use it.
Setting the reference to
nil
will reduce its reference counting by one (once it reaches zero, it will be deallocated from memory). When your class deallocated from memory, all class instance properties will be set tonil
as well.
How to do it...
Create two classes,
Person
andDog
, with a relation between them, as shown in the following code snippet (this code snippet has a memory issue called reference cycle):class Dog{ var name: String var owner: Person! init(name: String){ self.name = name } } class Person{ var name: String var id: Int var dogs = [Dog]() init(name: String, id: Int){ self.name = name self.id = id } } let john = Person(name: "John", id: 1) let rex = Dog(name: "Rex") let rocky = Dog(name: "Rocky") john.dogs += [rex, rocky] // append dogs rex.owner = john rocky.owner = john
Update the reference type of owner property in the
Dog
class to break this cycle:class Dog{ var name: String weak var owner: Person! init(name: String){ self.name = name } }
How it works...
We have started our example by creating two classes, Person
and Dog
. The Person
class has one-to-many relation to the Dog
class via the property array dogs
. The Dog
class has one-to-one relation to class Person
via the property owner
. Everything looks good, and it works fine if you tested, but unfortunately we did a terrible mistake. We have a retain cycle problem here, which means we have two objects in memory; each one has a strong reference to the other. This leads to a cycle that prevents both of them from being deallocated from memory.
This problem is a common problem in iOS, and not all developers note it while coding. We call it as parent-child relation. Parent (in our case, it's the Person
class) should always have a strong
reference to child (the Dog
class); child should always have a weak
reference to the parent. Child doesn't need to have strong
reference to parent, as child should never exit when parent is deallocated from memory.
To solve such a problem, you have to break the cycle by marking one of these references as weak
. In step 2, we see how we solved the problem by marking the property owner as weak
.
There's more...
The reference cycle problem can happen in situations other than relations between classes. When you use closure, there is a case where you may face a retain cycle. It happens when you assign a closure to a property in class instance and then this closure captures the instance. Let's consider the following example:
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 } } let heading = HTMLElement(name: "h1", text: "h1 title") print(heading.asHTML()) // <h1>h1 title</h1>
We have the HTMLElement
class, which has closure property asHTML
. Then, we created an instance of that class which is heading
, and then we called the closure to return HTML text. The code works fine, but as we said, it has a reference cycle. The instance set closure to one of its property, and the closure captures the instance (happens when we call self.name
and self.text
inside the closure). The closure in that case will retain self
(have a strong reference to the heading
instance), and at the same time, heading
already has a strong reference to its property asHTML
. To solve reference cycle made with closure, add the following line of code as first line in closure:
[unownedself] in
So, the class will look like this:
class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { [unownedself] 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 } }
The unowned
keyword informs the closure to use a weak reference to self instead of the strong default reference. In that case, we break the cycle and everything goes fine.