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.
Working with memory management and ARC
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 to nil as well.
How to do it...
- Create two classes, Person and Dog, 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.