Strong Reference Cycle in IOS, a brief Overview and how to avoid them
In iOS, memory is managed by a mechanism known as ARC, or Automatic Reference Counting. In laymen’s terms, Automatic Reference Counting mechanism works as follows: An Object exists in memory if its reference count is greater than 0. Each Object instance has its own reference count. Under the hood, it’s simply a number with a default value of 0. If another object or function holds a strong reference to that object, its reference count increases by the number of strong reference “holders”.
For example:
If an object is initialized from a function, the function references strongly to that object until the program leaves the scope of the function.
func instantiateTableView() {
let tableView = UITableView()
}
So, the UITableView object is instantiated in a function, it’s reference count is now 1. When the program exits the scope of the function, that table view object will be deallocated, since it’s reference count will decrease to zero.
Memory Management of properties
By default, properties hold a strong reference to an object instance in Swift and in Objective-C. This means that as long as an existing object holds a strong reference to another object, that object has a reference count larger than 0. If the holder object’s reference count drops to 0, the holder object gets deallocated from memory and the object being held loses 1 reference count. If the reference count of the object being held also drop down to 0, the object being held also gets deallocated from memory.Strong reference cycle
In the next code snippet, there are 2 classes: ClassA and ClassB, that both have a strong reference to each other, both object instances cannot be deallocated from memory. This causes memory leaks.
final class ClassA {
var classB: ClassB?
}
final class ClassB {
var classA: ClassA?
}
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let classA = ClassA()
let classB = ClassB()
classA.classB = classB
classB.classA = classA
}
deinit() {
debugPrint("ViewController deinitialized")
}
}
To recap, some custom view controller is being initialized and presented modally. In it’s internal logic, classes A and B are initialized, and then were given strong reference of each other to themselves. Strong reference cycle will happen immediately when the program leaves the scope of the viewDidLoad() method. View Controller does not hold a strong reference to neither ClassA or ClassB instances, but they hold a strong reference to each other, and they will exist in the memory as long as a current Process (App) is running. This is also known as a memory leak.
Finally, on to the bonus points...
Question: What happens when there are 3 or more objects, how does the memory leak happen then 🙂 Do all objects need to hold strong references to each other to cause a leak?
Well the answer is yes and no. Of course, the strong reference cycle will occur if all objects hold a strong reference to each other, just like with just 2 objects. But also, there is this: If there is a strong reference cycle between 2 objects, all other objects that are kept strongly by either of those two objects will also never be deallocated. And the more objects being kept in memory like that while cannot be accessed anymore, the bigger the leak is.
Example with 3 classes:
final class ClassA {
var classB: ClassB?
}
final class ClassB {
var classA: ClassA?
var classC: ClassC?
}
final class ClassC {}
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let classA = ClassA()
let classB = ClassB()
let classC = ClassC()
classA.classB = classB
classB.classA = classA
classB.classC = classC
}
deinitialize() {
debugPrint("ViewController deinitialized")
}
}
When this CustomViewController instance gets deallocated from memory, all 3 objects (class A, B and C) will never get deallocated. ClassC will also never be deallocated even though it not hold back strong reference to nether ClassA or ClassB. Why? Everything being held strongly by the two objects that are stuck in the strong reference cycle will never be deallocated. More objects, bigger the leak!
The Solution
In theory, a good programmer will provide a good development solution that follows a good architecture, patterns and principles, and he will avoid making Classes (Objects) dependent on each other. But if somehow, 2 objects indeed need to have a reference to each other, a simplest solution is to use weak declaration on the stored property. Declaring a stored property as a weak reference, does not increment that property’s reference count by 1. In other words, another object needs to have a strong reference to it, in order for the Object to exist, but a strong reference cycle is avoided between two objects that hold a reference to each other.
final class ClassA {
var classB: ClassB?
}
final class ClassB {
weak var classA: ClassA?
}
Quick modification in the 2nd snippet, just changing in ClassB that a classA stored property has a weak reference hold on it, the strong reference cycle is avoided, and both objects of classes A and B will be deallocated after program leaves the scope of viewDidLoad() method.
Comments
Post a Comment