This article explores the naming rules for memory management in iOS, the implementation mechanism for reference counting, and the internal implementation of weak.

This article is also published on my personal blog

This article was first published in 2015. Although MRC is rarely used today, ARC is not fundamentally different from MRC and it is important to understand the principles behind it.

Overview


Memory Management has always been an important topic in C languages, which do not have Garbage Collection like Java, and Memory Management is entirely the responsibility of the programmer. As a result, serious problems such as Memory leaks or Dangling can occur, which can be fatal to an application.

Objective-c, as an object-oriented language developed on the basis of C language, naturally has no memory management mechanism. Therefore, as iOS programmers, we also have to handle memory issues very carefully. However, with the arrival of ARC, all this has changed a lot.

Automatic Reference Counting (ARC) supported by the LLVM3.0 built-in compiler in iOS5 and Xcode4.2 implements Automatic memory management, as shown in the name. In simple terms, this is essentially a way to transfer memory management from the programmer to the compiler, although some features need runtime support.

Naming conventions in memory management


In contrast to ARC, we refer to manual memory management as MRC(Mannul Reference Counting). As the name of ARC and MRC shows, there is no difference in the nature of memory management between them. Memory is managed using Reference Counting. The difference is that in ARC memory management-related code is automatically inserted by the compiler when the code is compiled.

So here’s the problem

How does the compiler automatically insert memory management code under ARC? To put it more bluntly, how does the compiler know where it needs to insert relevant code?

First, you can think of releasing a member variable (strong) held by an instance object of the class in the dealloc method of the class.

So how does the compiler manage memory for local variables?

{
    NSString *str = [[NSString alloc] initWithFormat:@"test ARC"];
    NSLog(@"% @", str);
}
Copy the code

As we know, under ARC, the above code snippet will be processed by the compiler (just as an example, the final processing of the compiler may not be completely consistent) :

{
    NSString * str = [[NSString alloc] initWithFormat:@"test ARC"];
    NSLog(@"% @", str); [str release]; // The compiler inserts release}Copy the code

In the following code snippet, the compiler does not add any code for memory management:

{
    NSString *str = [NSString stringWithFormat:@"test ARC"];
    NSLog(@"% @", str);
}
Copy the code

Why does the compiler treat the above two pieces of code differently?

The obvious answer is that code 2 returns an Autorelese object, which has been put into memory management and therefore does not need to be processed by the compiler, whereas code 1 does not.

Well, the question seems to have been perfectly answered! However, all of this is analyzed from a human point of view. How does the compiler know that code 1 requires its managed memory and code 2 does not?

Ok, that’s what this section topic, “Naming Conventions,” addresses.

The method caller holds any method whose column name is prefixed with object if its return value is object:

  • alloc
  • new
  • copy
  • mutableCopy

Here’s an even stricter rule: Any method prefixed with init must follow the following rules:

  • The method must be an instance method;
  • The method must return typeidOr its parent class, superclass, subclass;
  • The object returned by this method cannot be autorelese, that is, the method caller holds the returned object.

“The method caller owns the object” means that the object’s memory needs to be managed by the caller.

The object returned by any method outside of this is not held by the caller, i.e. the autoRelease object should be returned.

Exceptions: Methods prefixed by allocate, newer, copying, mutableCopyed, and initialize are not included in the above rules.

Based on the above rules, the compiler can easily determine that code 1 needs to deal with the memory of the STR object, while code 2 returns an AutoRelease object and therefore does not need to deal with it.

In the coding process, you should strictly follow the above rules!

The question is, what if you simply don’t follow these rules during coding? First of all, you’re excluded from being a good programmer! In the MRC era, code that does not follow the above rules is extremely difficult to maintain in terms of memory management and is prone to memory leaks or multiple releases.

ARC is mixed with MRC

Since many projects have gone through the MRC to ARC era, there is a strong case for ARC and MRC to coexist in projects. Is there a problem if you don’t follow the naming rules?

The reason for this is that the alloc prefixed method is called in an ARC file. Based on the naming convention, the compiler assumes that the allocString method will return an object that it needs to release. The above ARC code will be processed by the compiler as:

allocString

allocString

Inside Reference Counting


As mentioned above, both ARC and MRC use Reference Counting to manage memory.

If you were to design a reference counting mechanism, what would you do? Well, that’s a good interview question! In fact, there are two answers to this question:

  • Manage reference counting within the object;
  • External structures (such as hash tables) are used to manage reference counts.

GNUstep’s Implementation of Reference Counting

GUNstep implements a Framework compatible with Cocoa Framework. As an open source, let’s take a look at how it handles reference counting:

alloc
obj_layout

Apple’s Implementation of Reference Counting

Now that Apple has sourced the relevant code, we can take a closer look. All of Apple’s source code can be found here: Apple Opensource.

First, let’s look at how Apple implements the Retain method:

retain
sidetable_retain

Before continuing, it’s worth introducing a new friend: SideTable

SideTable
RefcountMap
weak_table_t

RefcountMap is a simple map whose key is the object memory address and whose value is the reference count.

From the SideTable source code, we can also draw the following conclusions:

  • There are a number of globalSideTableInstance, which are stored in static member variablestable_buf; There are 8 such instances on iOS (SIDE_TABLE_STRIPE = 8)
  • All objects generated while the program is running are mapped totable_bufIn the correspondingSideTableInstance.

The reason why multiple SideTable instances exist and objects are mapped to different SideTable instances is to optimize the performance and avoid the reference table and Weak table in the SideTable being too large.

Going back to the sidetable_retain method above, it first finds the corresponding SideTale by the object’s address, and then increments the object’s reference count by one by RefcountMap.

The code for release, retainCount, and other related methods can also be found in this open source code and will not be covered here.

Simply put, Apple records Reference Counting through global map, where key is object address and value is Reference count.

So, what are the pros and cons of GNUstep versus Apple’s implementations? The GNUstep solution is simple and clear from the implementation point of view, while Apple’s solution can better control system memory usage, which is helpful for debugging.

Inside Weak


Weak is one of the most powerful tools ARC gave us. It can be used to eliminate delegate problems. This is because the object to which the weak pointer points is set to nil when dealloc is used. So how does the system handle weak?

As shown in the preceding code, the system calls objc_initWeak with the address of the weak pointer variable (&weakNum) and the object(num) used for assignment:

objc_initWeak
objc_storeWeak
weak_register_no_lock
objc_storeWeak
objc_storeWeak

weak_register_no_lock

weaktable

When it comes to weak, it is inevitable to say weaktable:

weak_entry_t
weak_entry_t

Then, how to find its corresponding entry through object in WeakTable?

Finally, the weak pointer is set to nil when the object to which the weak pointer points is dealloc. So what if the life of the weak variable ends before the object to which it points?

After the end of the life cycle of the weak variable, if the memory block used by the weak variable is reused and assigned a new value, and the above object is dealloc, if the corresponding weak variable is set to nil according to the entry in the Weak Table, then the reused variable will be cleared to 0. This is clearly inappropriate.

In the above mentioned pseudo code, we see that the objc_destroyWeak method is called at the end of the life cycle of the Weak variable weakNum, which, yes, is intended to solve this problem. The objc_destroyWeak method eventually calls the weak_unregister_no_lock method, which removes the weak variable from the Weak table.

Toll-Free Bridge cast


__bridge cast

In ARC, the conversion between id and void* is no longer arbitrary as in THE MRC era. In ARC, the __bridge cast is used to convert between the two. When converting an ID to a void*, it is not in ARC’s memory management scope and is more likely to happen.

__bridge_retained cast

The function of __bridge_retained is to allow the assigned variable to hold the object.

__bridge_transfer cast

__bridge_transfer causes the assigned object to be released after the assignment.

Under ARC, when a conversion is required between an Objective-C object and a Core Foundation object, you can use the following methods:

summary


There are a lot of questions about memory management that are worth discussing, and with Apple’s open-source code, all the secrets can be found in the source code.