ref – http://www.thomashanning.com/retain-cycles-weak-unowned-swift/
- By default, each reference, that points to an instance of a class, is a so-called strong reference.
- As long as there is at least one strong reference pointing to an instance, this instance will not be deallocated.
- When there’s no strong reference pointing to that instance left, the instance will be deallocated
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class TestClass { init() { print("init") } deinit { print("deinit") } } var testClass: TestClass? = TestClass() testClass = nil |
output:
init
deinit
Program ended with exit code: 0
testClass has a strong reference to an instance of TestClass. Hence TestClass instance is +1.
If we now set this reference to nil, the reference pointer will away from the TestClass object, and point to nil. TestClass instance is now 0.
Since there is no strong reference pointing to the object TestClass gets deallocated:
By the way, if you take a look at the console, you can see that everything is working fine because the deinit method will only be called by the system when the instance gets deallocated.
If the instance of TestClass was not deallocated, there wouldn’t be the message “deinit”. As we will discuss later, placing a log message inside of deinit is a very good way to observe the deallocation of an object.
Retain Cycle Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class TestClass { var next: TestClass? = nil init() { print("init") } deinit { print("deinit") } } var testClass1: TestClass? = TestClass() var testClass2: TestClass? = TestClass() testClass1?.next = testClass2 testClass2?.next = testClass1 |
Now, we have two reference variables called testClass1, and testClass2.
testClass1 reference variable point to a newly allocated object TestClass.
testClass2 reference variable point to a newly allocated object TestClass.
the object pointed to by testClass1, has a property called next, which points a TestClass object. It then points to the object referenced by testClass2.
same applies to the object pointed to by testClass2.
Hence, so far each object has 2 strong references pointing to it.
The situation is visualized in the following picture:
TestClass instance pointed to by testClass1, has ref count 2.
TestClass instance pointed to by testClass2, also has ref count 2.
Now say we are done using these objects and we set our reference variables testClass1 and testClass2 to nil:
1 2 |
testClass1 = nil testClass2 = nil |
But the two instances won’t get deallocated! You can see this because there are not “deinit” messages in the console. Why is this happening? Let’s take a look at the situation:
Each object has lost one strong reference, but because it is pointed to by the next property of each other, each object still has 1 strong reference pointing to it. Thus, both object still has +1.
This means these two objects won’t be deallocated. This is called a memory leak. If you have several leaks in your app, the memory usage of the app will increase every time you use the app. When the memory usage is to high, iOS will kill the app. That’s the reason why it’s so important to take care of retain cycles. So how can we prevent them?
Solution – using weak
Using so-called weak references is a way to avoid retain cycles. If you declare a reference as weak, it’s not a strong reference. Thus, by definition, a weak reference does not increment the reference count of an object. Only strong reference increment the count.
Let’s change our next property to weak, and see what happens:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class TestClass { weak var next: TestClass? = nil //Now this is a weak reference! init() { print("init") } deinit { print("deinit") } } var testClass1: TestClass? = TestClass() var testClass2: TestClass? = TestClass() testClass1?.next = testClass2 testClass2?.next = testClass1 |
From a reference count point of view:
Now, let’s set it to nil and see what happens.
1 2 |
testClass1 = nil testClass2 = nil |
Thus, now we set the reference to the objects to nil. This is -1 for the count. Due to weak having 0 effect on the reference count, the objects becomes 0 in reference count. Since the next property is weak, and we already niled the strong references testClass1 and testClass2, the two objects then becomes 0 reference count, and thus gets deallocated.
Only weak references are left and the instances will be deallocated.
Because only optionals can become nil, every weak variable has to be an optional.
unowned
Besides weak, there is a second modifier that can be applied to a variable: unowned. It does the same as weak with one exception: The variable will not become nil and therefore the variable must not be an optional. It must be a non-optional variable.
But as I explained in the previous paragraph, the app will crash at runtime when you try to access the variable after its instance has been deallocated. That means, you should only use unowned when you are sure, that this variable will never be accessed after the corresponding instance has been deallocated.
Generally speaking it’s always safer to use weak. However, if you don’t want the variable to be weak AND you are sure that it can’t be accessed after the corresponding instance has been deallocated, you can use unowned.
It’s a little bit like using implicitly unwrapped optionals and try!: You can use them, but in almost all cases it’s not a good idea.
Common scenarios for retain cycles: delegates
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class ParentViewController: UIViewController, ChildViewControllerProtocol { let childViewController = ChildViewController() func prepareChildViewController() { childViewController.delegate = self } } protocol ChildViewControllerProtocol: class { //important functions... } class ChildViewController: UIViewController { var delegate: ChildViewControllerProtocol? } |
Essentially,
1 |
weak var delegate: ChildViewControllerProtocol? |
So remember that you should in almost all cases declare delegates as weak to prevent retain cycles.
Here’s a look at the reference count perspective:
Retain cycles in closures
First, some semantics…
As a function parameter with explicit capture semantics:
1 2 |
array.sort({ [unowned self] (item1: Int, item2: Int) -> Bool in return item1 < item2 }) |
As a function parameter with explicit capture semantics and inferred parameters / return type:
1 2 |
array.sort({ [unowned self] in return $0 < $1 }) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class TestClass { var aBlock: (() -> ())? = nil let aConstant = 5 init() { print("init") aBlock = { print(self.aConstant) } } deinit { print("deinit") } } var testClass: TestClass? = TestClass() testClass = nil |
We can see in the logs that the instance of TestClass will not be deallocated. The problem is, that TestClass has a strong reference to the closure and the closure has a strong reference to TestClass:
You can solve this by capturing the self reference as weak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class TestClass { var aBlock: (() -> ())? = nil let aConstant = 5 init() { print("init") aBlock = { [weak self] in //self is captured as weak! print(self?.aConstant) } } deinit { print("deinit") } } var testClass: TestClass? = TestClass() testClass = nil |
In reference count perspective:
If we were to strong references, the object strongs the closure object, and the closure object strongs the TestClass object. Thus, when the object reference to TestClass gets pointed away, our TestClass still has +1 reference pointed to it by the closure.
If we are to use weak reference in the closure, TestClass object only has +1 reference count due to its instance reference. When we point away the instance reference, then it becomes 0, and thus everything gets deallocated, including its reference to the closure.
Local reference to closures
However, there won’t always be a retain cycle when using closures! For example, if you are just locally using the block, there is no need to capture self a weak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class TestClass { let aConstant = 5 init() { print("init") let aBlock = { print(self.aConstant) } } deinit { print("deinit") } } var testClass: TestClass? = TestClass() testClass = nil |
The reason is that variable aBlock is created and pushed onto the local stack. Yes, it strongs the closure object. But when the local stack goes out of scope every local variable gets popped (including the strong reference to the closure). Thus, when it gets popped, all you’re left with is the closure object in the heap, with its strong reference back to TestClass so that it can use it.
Then once the closure object finishes executing, it sees that nothing is referencing it. Thus it gets deallocated, including its strong reference to TestClass.
The same holds true when use for example UIView.animateWithDuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class TestClass { let aConstant = 5 init() { print("init") } deinit { print("deinit") } func doSomething() { UIView.animate(withDuration: 5) { let aConstant = self.aConstant //fancy animation... } } } var testClass: TestClass? = TestClass() testClass?.doSomething() testClass = nil |
The idea here is the same. Locally, we’re simply referencing UIView’s class function. And that class function references the closure object. Once our local stack gets popped, the strong reference to UIView’s class function gets niled.
a child class should not have a strong reference to the parent class