Solving Retain Cycles (feat. ARC)

Sue Cho
Sue Cho
Oct 24, 2020 · 5 min read

Article with music: Irreplaceable — Beyoncé

“You must now know ‘bout me” — weak reference counting

One of the issues that I don’t enjoy dealing with is memory-related. Probably because the journey to find where the leak occurred is complicated. It often happens when dealing with closures because I tend to capture all `self`s as weak by rote. I noticed myself falling into some sort of error a few days ago. Turned out, I used [unowned self] and tried to use that closure after freeing the class instance. (What was I thinking? Dunno🤷‍♀️) So it occurred to me that I should take some time to revise retain cycle & ARC.

Understanding ARC

The best way to understand ARC is to read the swift document. ARC was first introduced to this world at 2011 WWDC and memory management for Swift codes were all done by it.(You were able to choose whether to use ARC for Objective-C but it’s now set as default.) ARC tracks and manages the memory use of application which is not subjected to OS but the compiler. Reference counting applies only to reference types(obviously🤷‍♀️), instances of classes to be specific. Knowing when deallocation takes place is crucial because a reference type instance is capable of accessing multiple places at the same time.

In other programming languages like Java, memory management is done by what is known as a Garbage Collector. The biggest difference between ARC and GC is ‘when’ the reference counting is done. ARC does reference counting during compilation while GC does it during run time.

Now, you might wonder how reference counting can be done at compile time when things are done dynamically within the application. Well to understand the concept, we should understand how MRC used to work back when only Objective-C was our only option. But since this story is not focused on ARC, let me just quote what’s written in Apple’s Transitioning to ARC Release Notes.

Instead of you having to remember when to use retain, release, and autorelease, ARC evaluates the lifetime requirements of your objects and automatically inserts appropriate memory management calls for you at compile time.

Now back to ARC vs GC. Because ARC has finished reference counting is done in advance, you can predict when the memory will be freed. However there’s a risk of failing to deallocate the memory forever if you don’t know some rules ARC adopts.(We’re going to get to this part soon🥳) GC don’t really have any rules for freeing memory so you know that an instance will be freed at some point. The problem is you don’t know when exactly that will happen and the need for extra resources to monitor the memory usage.

When does strong reference cycle occur?

There are 2 cases of strong reference cycle.

  • Two class instances referencing each other.
  • When closure is a property of a class instance. (When closure captures self)

The first case of strong reference cycle is fairly easy to solve. Let’s take a look. Underneath I have created 2 classes each named ‘Person’ and ‘Car’. The car property of Person is optional because a person might not own a car and the person property of Car is optional because a car might not have an owner (yet). What we expect the result to be is like this.

Sue : initialized
Lamborghini : initialized
Sue : deinitialized
Lamborghini : deinitialized

But nope. If you run the code underneath, all you get is 2 initialized print statements. So we know there’s a memory leak because none of the deinits were called. What happened?

If we go over line by line starting from #30, this is what happened.

#30 : Person instance reference counting = 1
#31 : Car instance reference counting = 1
#33 : Car instance reference counting = 2
#34 : Person instance reference counting = 2
#36 : Person instance reference counting = 1
#37 : Car instance reference counting = 1

So after line 37, there’s no way to access any of the instances so the memory will never be deallocated. This is due to strong reference cycle. It’s easy to break this cycle. All you need to do is make one of the reference as weak/unowned. In this case using ‘weak’ seems more appropriate as there’s always a possibility of assigning nil to either sue or aventador.(The owner might just sell the car🤷‍♀️) So to give you a sample code on how to fix the above, here’s a suggestion.

Let’s say you modified the above code by adding weak keyword infront of var owner: Person?. Then you can get the result we expected with the following sequence.

#30 : Person instance rc = 1
#31 : Car instance rc = 1
#33 : Car instance rc = 2
#34 : Person instance rc = 1
#36 : Person instance rc = 0(freed) & Car instance rc = 1
#37 : Car instance reference counting = 0 (freed)

(#36) So when ‘sue’ is being deallocated from the memory, the reference counting of Car instance goes down by 1. And when nil is assigned to ‘sue’, aventador’s property ‘owner’ automatically changes to nil.

The reason strong reference cycle occurs in this case is because closures are also reference type like classes. If closure is used as a property of a class instance than two reference types are pointing(referencing) each other. This is where strong reference cycle takes place. Let’s look at another example

When closure is called through description property, the body of description closure captures the instance it belongs to because it accesses properties of the instance like self.code. Cases of capturing also occurs when the closure calls a method of the instance. So by capturing ‘self’(the instance closure belongs to) it creates a strong reference cycle. This can be resolved by defining a capture list as part of the closure’s definition.

Capture List

Remember that I mentioned there are some rules ARC adopts when we compared ARC with garbage collector? A capture list defines the rules to use when capturing reference types inside the closure’s body. Like we have done before, we can declare captured reference as weak or unowned. Unowned is recommended when the closure and the instance it captures will always refer to each other, and will always deallocated at the same time. To simply put, if there’s no need to use the closure once the class instance is deallocated, then use unowned. So for our case, we can fix the closure as below.

lazy var description: () -> String { [unowned self] in
if let text = self.text {
return "\(self.code) : \(text)
} else {
return "\(self.code) : Success"
}
}

The Startup

Get smarter at building your thing. Join The Startup’s +792K followers.

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +792K followers.

Sue Cho

Written by

Sue Cho

iOS Developer #DobbyIsFree

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +792K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store