Published

Demystifying Retain Cycles in ARC

Retain cycles in ARC are kind of like a Japanese B-horror movie. When you start as a Cocoa/Cocoa Touch developer, you don’t even bother about their existence. Then one day one of your apps starts crashing because of memory leaks and suddenly you become aware of them, and start seeing retain cycles like ghosts everywhere. As the years pass, you learn to live with them, detect them and avoid them… but the final scare is still there, looking for its chance to creep in.

One of the biggest disappointments of swift for many developers (including me) was that Apple kept ARC for memory management. ARC unfortunately doesn’t include a circular reference detector, so it’s prone to retain cycles, thus forcing the developers to take special precautions when writing code.

Retain cycles are a obscure topic for iOS developers. There are lot of misinformation in the web [1][2], to the point of people giving wrong suggestions and “fixes” that could even potentially lead to problems and crashes in your App. In this article, I would like to shed some light on the subject matter.

Some (brief) theory

Memory management in Cocoa dates back to Manual Retain Release (MRR). In MRR, the developer would declare that an object must be kept in memory by claiming ownership on every object created, and relinquishing it when the object was not needed anymore. MRR implemented this ownership scheme by a reference counting system, in which each object would be assigned a counter indicating how many times it has been “owned”, increasing the counter by one, and decreasing it by one each time it was released. The object would then cease to exist when its reference counter reached zero. Having to take care of reference counting manually was really an annoyance for the developer, so Apple developed ARC (Automated Reference Counting) to free the developers of having to manually add the retain and release instructions, allowing them to focus in the problem being solved by the App. Under ARC, a developer will define a variable as “strong” or “weak”. A weak variable will not be retained by the object declaring it, whereas a strong declaration will retain the object and increment its reference count.

Why should I care?

The problem with ARC is that it’s prone to retain cycles. A retain cycle occurs when two different objects contain a strong reference to each other. Think of a Book object that contains a collection of Page objects, and that every Page object has a property pointing to the book the page is contained in. When you release the variables that point to Book and Page, they still have strong references among them, so they won’t be released and their memory freed, even though there are no variables pointing to them.

Unfortunately, not all retain cycles are so easy to spot. Transitive relationships between objects (A references B, which in turn references C, which happens to reference A) can lead to retain cycles. To make things worse, both Objective-C blocks and Swift closures are considered independent memory objects, so any reference to an object inside a block or closure will retain its variable, thus leading to a potential retain cycle if the object also retains the block.

Retain cycles can be potentially dangerous for an App, leading to high memory consumption, bad performance and crashes. However, there is no clear documentation from Apple about the different scenarios in which a retain cycle may occur, and how to avoid them. This has lead to a number of misconceptions and bad programming practices.

Use case scenarios

So, without further ado, let’s analyze some scenarios to determine whether or not they cause a retain cycle and how to avoid them:

Parent-child object relationship

This is the classic example of retain cycle, and unfortunately, the only one addressed by the Apple documentation. It’s the example of the Book and the Page objects I described before. The classic solution for this situation is defining the variable representing the parent in the Child class as weak, thus avoiding the retain cycle

The fact that parent is a weak variable in Child forces us to define it as an optional type in swift. An alternative for not having to use an optional is declaring parent as “unowned” (meaning that we don’t claim ownership or memory management on the variable). However, in this case we must be extremely careful to ensure that Parent doesn’t become nil as long as there is a Child instance pointing to it, or we will end up with a nasty crash:

In general, the accepted practice is that the parent must own (strongly reference) its children objects, and they should only keep a weak reference to their parent. The same applies for collections, they must own the objects they contain.

Blocks and closures contained in instance variables

Another classic example, though not so intuitive. As we explained before, closures and blocks are independent memory objects, and retain the objects they reference, so if we have class with a closure variable, and that variable happens to reference a property or method in the owning object, there would be a retain cycle because the closure is “capturing” self by creating a strong reference to it.

class MyClass {
lazy var myClosureVar = {
self.doSomething()
}
}

The solution in this case implies defining a “weak” version of self and sending that weak reference to the closure or block. In Objective-C we will define a new variable:

Whereas in Swift we just need to specify the “[weak self] in” as the closure starting parameter:

var myClosureVar = {
[weak self] in
self?.doSomething()
}

This way, when the closures reaches the end, the self variable is not strongly retained, so it gets released, breaking the cycle. Note how self becomes an optional inside the closure when declared weak.

GCD: dispatch_async

Contrary to what’s usually believed, dispatch_async per se will NOT cause a retain cycle

dispatch_async(queue, { () -> Void in
self.doSomething();
});

Here, the closure has a strong reference to self, but the instance of the class (self) does not have any strong reference to the closure, so as soon as the closure ends, it will be released, and so no cycle will be created. However, sometimes it’s (incorrectly) assumed that this situation will lead to a retain cycle. Some developers even get to the point of recommending all references to “self” inside a block or closure to be weak:

dispatch_async(queue, {
[weak self] in
self?.doSomething()
})

This is not a good practice for every single case in my opinion. Let’s suppose that we have an object that launches a long background task (like downloading some content from the network), and then invokes a method of “self”. If we pass a weak reference to self, our class could end its lifecycle and get released before the closure ends, so when it invokes the doSomething() method, our class instance doesn’t exist anymore, so the method never gets executed. The proposed solution (by Apple) for this situation is declaring a strong reference to the weak reference (???) inside the closure:

Not only do I find this syntax disgusting, unintuitive and tedious, but I think it defeats the entire purpose of having closures as independent processing entities. I think it’s much better to understand the lifecycle of our objects and know exactly when should we declare a weak inner version of our instance and which are the implications for the lifetime of our objects. But then again, this is something that distracts me from the problem my App is solving, it’s code that wouldn’t need to be written if Cocoa didn’t use ARC.

Local closures and blocks

Closures and blocks local to a function, not referenced or contained in any instance or class variable will NOT cause a retain cycle per se. One of the common examples of this is the animateWithDuration method of UIView:

As with dispatch_async and other GCD related methods, we should not worry about retain cycles in local closures and blocks not referenced strongly by the class instance.

Delegation scheme

The delegation scheme is one of the typical scenarios where you want to use a weak reference to avoid a retain cycle. It is always a good practice (and generally safe) to declare the delegate as weak. In Objective-C:

@property (nonatomic, weak) id <MyCustomDelegate> delegate;

And in swift:

weak var delegate: MyCustomDelegate?

In most cases, the delegate of an object has instantiated the object or is supposed to outlast the object (and react to the delegate methods), so under a good class design we should not find any problems regarding the lifetime of objects.

Debugging retain cycles with Instruments.

It doesn’t matter how hard I try to avoid retain cycles, sooner or later I forget to include a weak reference and accidentally create one (thanks for nothing, ARC!). Luckily, the Instruments application included in the XCode suite is a great tool for detecting and locating retain cycles. It is always a good practice to profile your App once the development phase is over and before submitting it to the Apple Store. Instruments has many templates for profiling different aspects of the App, but the one we’re interested in is the “Leaks” option.

Once Instruments opens, you should start your application and do some interactions, specially in the areas or view controllers you want to test. Any detected leak will appear as a red line in the “Leaks” section. The assistant view includes an area where Instruments will show you the stack trace involved in the leak, giving you insights of where the problem could be and even allowing you to navigate directly to the offending code.

Published

Share your thoughts

24 Comments

Regarding the comment about long running dispatch_async tasks. I agree you don’t need weak self – you will not get retain cycles, however most of the time I argue weak still has advantages.

The use case normally invokes kicking off some backgound work on a view controller. If the view controller becomes nil this usually because the user had finished or cancelled that task by poping the controller off the stack. If you used a strong reference the view controller (and all data and properties) will stay in memory until that task is finished. With a weak var the controller will be dealocated instantly.

Hi Robert, thanks for your comment.
I don’t really see any advantage in using weak, not at least as a general practice, could you give us an example?. Apart from ViewControllers, any object could launch a long running background task, and if you want that object to respond to this task, you better have it in memory somehow. I can think of many situations where not responding properly to the results of the background task could result in a wrong behavior, but if there is no retain cycle, it can do no harm to keep the object in memory for just a little longer until it can at least respond to the closure or block, right?

1 You show a loading spinner –> 2 start a download in the background –> 3 after the download finishes, you remove the spinner and update the data in the view.

If the user leaves the controller between 2 and 3, there is no sense in keeping the view controller alive. Everything the controller would do at that moment would be to remove the spinner, update the collection-/tableView and get released afterwards. All of this expensive calculations are not needed anymore, because the user is somewhere else in the app by that time.

That’s a good one ;), but even in that situation, I would like to have control on how and when that closure or block is executed. As the deallocation of that VC could depend on a lot of other conditions (including other strong references and ARC own mysticism), I won’t leave it to ARC to decide if my block is going to be executed or not. Think for a moment that the VC is not deallocated (for any reason, it could happen) but the user is not there anymore, it could lead to a lot of extra instructions and useless calculations in the best case, and a crash in the worst case. It’s just my personal opinion, but I agree is perfectly fine if it works for you.

Well, the block is executed anyway, as it normally is not retained by the controller, but hold alive by the URLRequest.
The most important Pro for a weak controller reference is, that when the block is called, [weakSelf.tableView reloadData] will do nothing expensive, because weakSelf is nil. Other (for example caching-)code, that has nothing to do with the controller itself will still be executed in the block, so you still have full control. You just don’t keep the controller alive to execute this one last expensive calculation, before it can be released.

Hi Kishore,
Thanks. Actually, I can’t recommend you any good book about blocks because I learned about them by reading the Apple documentation when they came out and practicing a lot. I plan on releasing a short tutorial on blocks and closures, but if you have any specific question about them, I’ll be more than happy to answer them if I can.

If you invoke a block or a method containing a block parameter on an instance or class, you are calling it directly on that class or instance. Your object (self) will not keep any reference to it, because it will be local to your method or function unless assigned to an instance or class variable.

I.E: When you call UIView.animateWithDuration:animations:, you are calling UIView’s class method directly, self never creates or keeps a strong reference to this object. For that to happen you need to assign a variable of your current instance (self) to that block or closure, and that’s when you can probably create a retain cycle.

About the leaks tool in Instruments, I don’t think the leaks track will usually find retain cycles. I believe ‘leaks’ works by searching your address space for values that look like pointers. If it can’t find the address of an object in that search it marks the object as a leaked. In the case of a retain cycle, it might not find the leak because both objects have references to each other. The address space search will find that reference and thus not mark it as a leak.

A better tool to find these retain cycles is to use the ‘allocations’ track that comes along with the leaks template:
1. Choose a starting state (e.g. Initial view after app launch)
2. Do an operation in your app (e.g. Open a view)
3. Go back to the starting state (e.g. Go back to the previous view)
4. Click the ‘mark generation’ button
5. Repeat a few times

In the absence of leaks, retain cycles, and abandoned memory, the allocations view will show 0 objects persisting in each generation (each generation shows of a diff of all objects that didn’t exist in the previous generation) and your total allocations will not be growing (except perhaps for the first time you mark generation). If you have any leaks/cycles/etc., instead you get a list of new objects that you can select to view the stack (and all of the retain/releases that happened). You can pair up the retain/release calls until you’re left with only a single retain (or more if there are multiple issues). The stack for that retain is where the retain cycle was created.

Thank you Terry.
Actually, I think that “Leaks” main purpose is finding retain cycles. In fact, when you are profiling using that template, under the “Leaks” options you can find “Leak cycles” or “Retain cycles” or something like that (I don’t have instruments open right now, sorry). I agree with you that it will probably not catch every single cycle, but it is the tool for doing that.

For hard to spot retain cycles, of course an analysis of allocations is useful, but it is really time consuming, so I only use it when I detect that my App is allocating an ever-growing quantity of memory and I suspect there is a retain cycle that has not been detected.

Thanks for the great article Ignacio, these are really the most confusing use cases. By the way, for people who haven’t noticed yet: Xcode has a new build setting which causes a warning when there is “Implicit retain of ‘self’ within blocks”. Turning this on lets us bust some myths, too :]

I just have one small comment about giving ARC a hard time. Retain cycles existed in MMR. For instance the case with an object retaining an instance to a block that references self: if the owner of that object released it, it would still leak since the block bumped its retain count.

Hi Seb, yes you are completely right, but with MMR, you were responsible for not causing retain cycles (I.E: retaining a block that shouldn’t be retained), whereas in ARC, you write code and then have to worry about “removing” the retain cycles that get created because of ARC.

Your wrote: Under ARC, a developer will define a variable as “strong” or “weak”. A weak App will not be retained by the object declaring it, whereas a strong declaration will retain the object and increment its reference count.

There is a reason Apple proposes the solution with “declaring a strong reference to the weak reference inside the closure”.

If you have more than one call to ‘self’ inside the closure it is possible that ‘self’ disappears after the first call because it’s only weakly held inside the closure. Let’s say you have two lines in the closure, ‘self?.test()’ and ‘self?.test2()’ (‘self’ being weak) and let’s assume that after the first line, self disappears because actions on another thread lead to the deallocation of the object it pointed to. Of course, your app won’t crash because you’re correctly using implicit unwrapping with ‘?’ on the second line. But what if executing the first line and not the second leaves your application in an unstable state? To ensure application integrity but also to make it easier to reason about application state, the two lines should execute both or not at all. Using the pattern with strongSelf ensures exactly this: Once strongSelf is set you can be sure that it will be around for the whole closure. You’re right, this pattern is not a typographically beautiful one but a lot safer than omitting it if you have more than one line referencing self.

First of all: This is a very interesting blog post. It is really well written and technically absolutely correct. I just want to add a couple of interesting aspects here.

You may be disappointed that Apple kept reference counting (RC) for Swift, but reference counting is not a bad concept in general, considering that it still easily beats the best garbage collector (GC) in performance for any large scale project. Something I often get reminded of whenever I have to work with Java, which has one of the best, most advanced GCs in the whole world and still, when I see the app has ugly performance hiccups, guess who’s causing them most of the time according to profiler. You will usually not see such hiccups in Mac/iOS apps and if you do, they are easily avoidable by small code changes. And it’s not just performance, RC apps also have a far smaller memory footprint on average as dead objects die a lot sooner.

On the other hand RC is definitely more comfortable and less error prone than manual memory management, something I get reminded of whenever I have to work with C code, where even manual RC is missing. I even built a simple RC scheme in C countless of times, as without the ability to retain objects, you are doomed to constantly copying them.

Then you said that RC forces developers to take special precautions when writing code. The problem with a GC system is, that developers usually never really think of memory management at all and they also don’t care about retain cycles, as they are generally harmless. However, consider this:

You load a UI form that loads another 4 MB of additional resource to memory (e.g. images displayed in a table), all strong references, of course. Among these resources is a tiny image, just 4 KB, that has a back reference to the UI form, also strong. that is a retain cycle, but as Java has a GC, who cares, right? Now you pass that image to some closure and this closure passes the image to some method and this method stores the image to some global ArrayList as you will need it later on – it’s just a 4 KB image, after all. But wait, this 4 KB image retains the UI form and thus another 4 MB of resources. Ouch!

You can also create such a problem easily with closures (called Lambda Expressions in Java), the delegate pattern, or (more Java-like) the listener pattern, which all uses strong references everywhere. Such an error is almost impossible to “see” while writing code and it’s still hard to find even with a profiler and a debugger. And it may be even harder to fix as fixing may force you to redesign large parts of your app.

If you look at Android, the apps are written in Java but the Android UI API is often not like anything you know from the Java standard library. Very often you might be annoyed by the fact that you only have limited access from one part of your app to other parts (at least not without ugly tricks like singletons or public static constructs) and that is by intention: Forcing developer to build smaller, independent object graphs, that can be discarded altogether when a UI goes out of sight. Still you may even have to actively break “retain cycles” in “onDestroy()” (e.g. by removing a listener), as you cannot just rely that everything will go out of scope and the GC will clean it all up for you – this will only work in very simple apps.

Every now and then you will even come across weak references or be forced to use them yourself. Yes, these really do exist in Java (already since 1.2!) and in some cases, the only way to get clean memory management is using a weak reference. However, compared to Swift or Obj-C, weak references feel super yucky in Java, a bit like “Ewww! I touched a weak reference! I have to go and wash my hands.” You cannot just make any reference weak, weak reference are wrapper classes, similar to NSValue, the wrapper itself is strong but the object it wraps is not kept alive by it and once collected, the wrapper returns null instead of an object reference.

The conclusion is: Good app developers always have to think about memory management, whether your language use RC, GC, or neither one, whether memory management is automated, semi-automated or manual. There is no system that would allow you to completely ignore the existence of memory management. The more complex and automated the system is, the longer you may get away not wasting a thought about it as long as your app won’t get too big, too complex and not use too many resources, but you’ll always trade this ignorance for less performance, a higher memory footprint, and less abilities to actively influence the memory management within your code.

Hi Markus,
Thanks for your comment. You made some good points here, and of course there’s no perfect memory management system, but I’m afraid I can’t completely agree with you.

Yes, it’s true that you can possibly create memory issues in GC systems such as Java, but it’s really difficult to do so, and you need to do some really weird stuff. In ARC, just forgetting a weak in a delegate will lead you to a retention cycle. Also, I agree that you need to be careful with the memory management in every language out there, but the problem is that, with languages such as C, you are responsible for allocating (alloc) and releasing (free) your memory, but you KNOW you must do it, and it follows a simple rule: you allocated this memory, make sure you deallocate it. That doesn’t hold true in ARC. You have to explicitly think: “is this variable referenced strongly in a place that can lead to a retention cycle? is it included in a transitive relationship with this class? or pointed by a variable somewhere else?”, and as you can see in the article, it sometimes can be tricky or not trivial at all.

My conclusion is: ARC is not perfect. In my humble opinion, it wouldn’t be that hard for Apple to include a cycle retention detection algorithm. This has nothing to do with being like “ooooh, I don’t want to touch a weak variable… bla bla bla” as you stated. This has to do with the fact that I expect from a modern language like swift the ability to allow me to focus in the problem I’m solving, like many many other languages out there. And I would rather have the “peace of mind” of GC systems or the explicit control of C-like systems. Just my opinion, though.

Webmentions

Guide: Use JavaScript in Swift – jjv360's BlogDecember 6, 2016

[…] JavaScriptCore, we don’t want any strong references to our own class, since it would cause a retain cycle. All it means for our app is that self could be null at some point, so we tell it to ignore nulls […]