ARC under the hood

Igor Sorokin (srk1nn)
5 min readJan 25, 2021

Hi everyone ✋🏼 Today we are talking about memory management. Discuss a little bit about MRC and consider ARC in details.

As you already know, first there was MRC, when programmers managed the reference counters manually.

A little bit about MRC:

  • retain — increments the receiver’s reference count
  • release — decrements the receiver’s reference count
  • copy — make deep copy of object (NOTE: class must conforms to NSCopying and copy returns immutable object)
  • assign — holds plain object’s address. It uses for value types and also for objects (like unowned in Swift), because it takes object’s pointer and doesn’t affect to reference counting system.

Next step was transition from MRC to ARC. Why we need ARC?

What ARC is really do, it just put retain / release in your code at the compile time. So it protects against human carelessness, helps programmers write less code and code becomes more readable.

Firstly, Apple introduced limited support (ARCLite) for iOS 4, and complete support ARC in iOS 5.

ARCLite is ARC but without zeroing weak references.

Zeroing weak references is a feature in Objective-C ARC that automatically clears (sets to nil) weak-reference local variables, instance variables, and declared properties immediately before the object being pointed to starts deallocating. This ensures that the pointer goes to either a valid object or nil, and avoids dangling pointers. Prior to the introduction of this feature, “weak references” referred to references that were not retaining, but were not set to nil when the object they pointed to was deallocated (equivalent to unsafe_unretained in ARC), thus possibly leading to a dangling pointer. The programmer typically had to ensure that all possible weak references to an object were set to nil manually when it was being deallocated. Zeroing weak references obviates the need to do this.

With ARC we have new attributes:

  • strong — it keeps a firm hold on that instance, and does not allow it to be deallocated for as long as that strong reference remains.
  • weak — is a reference that does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance. Weak is more safety, cause it operate optional object.
  • unowned(safe) — simply unowned in Swift, work like weak, but marking a value as unowned doesn’t make it optional. Becomes with complete ARC support, avoids dangling pointers.
  • unowned(unsafe) or unsafe_unretained — it’s Objective-C attribute for bridging with Swift unowned. Used in ARCLite and doesn’t avoids dangling pointers.
Fig. 1

So how this reference counters are located at memory?

Before Swift 4

Reference counters was located in memory with object. It made accessing them faster, but it caused zombies. Zombie — it’s object that deinited but not deallocated from memory. For example, we have class User in memory, reference counters located with object Fig. 2

Fig. 2

The class, properties, and reference counters are stored inline. As I said, it allows somewhat faster access than storing the data in the external chunk of memory. Let’s suppose a User is referred by one weak reference, and after some time, a strong counter comes to zero while the weak count is still non-zero, Fig. 3.

Fig. 3

In this situation, the object is intended to be deleted from memory by ARC. Despite this fact causes the object’s deinitialization, its memory is not deallocated. The weak counter is decremented when another object accesses the destroyed one by a weak reference. When a weak counter reaches zero, memory is finally deallocated. It means that our user becomes a zombie object that may occupy memory for a long time.

After Swift 4

Apple introduce improvement for ARC, that solves this problem. Side Tables! The Side Table is a separate chunk of memory which stores additional object’s information. Currently, it stores reference counters and flags. But Apple make some optimizations. When object has only strong reference, it located inline right before. But when an object has weak ones, it creates side table in memory and this memory section, that early contains strong reference counter, start points to that table, Fig. 4.

Fig. 4

Both the object and side table have a pointer to each other. Another thing to draw the attention is that weak references now point directly to the Side Table, whereas strong and unowned ones still point directly to the object. This allows the object’s memory to be fully deallocated. And it also means that unowned works faster than weak.

What if I told you that there is a mechanism that works like ARC, but in which the retain cycle does not arise? This Garbage Collection mechanism is also based on reference counting, but unlike ARC it works in runtime. It analyzes the object graph and if from one object it can’t get to another, it understands that there is a retain cycle and the objects should be removed. But that’s another story…

If you have any remarks, write them in the comments 👇🏼

Useful links:

--

--