Retain Cycles in Swift

Retain cycles is a very interesting topic.

What picked my brain about retain cycles in Swift though is the actual absence of the warning you get when capturing self strongly inside of a block unlike when writing purely in Objective-C. Let me demonstrate what I am talking about. Let’s assume we are building a simple calculator app that only supports 4 mathematical operations: add, subtract, multiply, and divide. Let’s also assume that in place of a regular method, we will write a block that will perform the calculation for specified operation and set the text of the UILabel to the resulting value.

First of all, let’s declare an enum to keep track of all supported operations:

Next, let’s declare a block property that takes 3 parameters, Operation and two floats (don’t mind the UILabel outlet for now):

To simplify things, let’s override the getter of our calculationBlock to perform the calculation and set label’s text to the result of the calculation:

The problem with this code is that (specifically in Objective-C), the compiler will warn you about potential retain cycle due to capturing self strongly inside of the operationBlock.

You can clearly see the offending line above. The problem is relatively clear, we declared a block property inside of our view controller and then inside of the operationBlock captured self. Both objects have strong pointers to each other, thus creating a retain cycle and a memory leak.

But I knew about this problem already from my Objective-C experience. The interesting thing however, is that if you recreate this code in Swift, the compiler never complains about the retain cycle.

This behavior captured my curiosity because naturally at first, I wanted to assume the compiler is doing something special in Swift, hence adding or assuming, if you will, unowned self by default.

However, according to Brian Lanier, we are definitely creating a retain cycle and a memory leak when capturing self strongly in Swift. You can watch the 2014 Session 403 or check out the actual transcript here.

So what gives? Why aren’t we getting warnings then?

To all who are interested, here is a quick rundown of ARC and how it works. It’s important to note that Reference Counting applies to instances of a class because they are stored and passed by reference. Every time you create an instance of a class, ARC will allocate memory to store information (type for example) about that instance. When the instance is no longer needed, ARC will destroy it. There is no need to manually keep track of instance count anymore… well, unless you are working with old projects that still rely on MRC to manage memory manually.

At the heart of the problem, it’s not your variables per se that create the retain cycle (you can set them to nil), it’s the references between instances of a class that prevent the deallocation of each other. So both objects will live in memory indefinitely even though no variables are pointing to them. This is something, unfortunately, ARC isn’t smart enough to figure out (at the time of this article). We manage, or help ARC in this case by declaring certain references as weak. An example would look like this:

When you recycle your iPhone, for a brief period of time, it will exist without an actual owner of type Person. That’s why the person variable on iPhone class is declared as weak. In this case if the person instance is deallocated, ARC will set the reference (var) to nil automatically and thus eliminate the retain cycle and the memory leak cased by it.

At this time, I would like to briefly mention unowned reference. The big difference between weak and unowned is that unowned reference is assumed to always have a value.

Let’s turn our attention to closures.

Just like classes, closures are reference types in Swift and according to Apple require use of capture lists to break retain cycle and a memory leak.

So the above example code for func setupOperationClosure should use unowned reference to ViewController instance or (self) like so:

In this case, unowned reference to self is appropriate since both self and operationClosure variable are pointing to each other and will be destroyed at the same time.

It’s quite interesting to me how the absence of the warning sent me down confirming that the things I knew about retain cycles in Objective-C are still relevant in Swift. Quite honestly I am not sure why the compiler doesn’t warn us about the problem like it does when writing in Objective-C, but rest assured, retain cycles are real even if there is no warning.

I would recommend watching the above mentioned WWDC video as well as read Swift book by Apple. Both are a great resources for anyone interested in the inner workings of Swift.

Retain Cycles in Swift