📜 ⬆️ ⬇️

Automatic Reference Counting: Part 1

Hello colleagues.

I have been reading blogs and articles of foreign developers for iOS for a long time. And the other day came across a curious, rather detailed article about Automatic Reference Counting from a developer named Mike Ash .
The article is quite large, because the translation made by me will be divided into several parts. I hope that I will be in 2 parts.


Since Apple announced this innovation, readers have encouraged me to write about the Automatic Reference Counting (ARC) mechanism. The time has come. Today let's talk about Apple's memory management system: how it works and how to make it work for you.
')
Concept

Clang Static Analyzer is a really useful tool for finding memory management errors in your code. If you and I are similar, then looking at the output of the analyzer, we think: “If an error is detected by the analyzer, why not fix it instead of me?”

In short, this is the essence of ARC. Memory management rules are built into the compiler. But instead of using them to help the developer look for errors, the ARC mechanism simply inserts the necessary calls. And that's all.

The ARC mechanism is located somewhere between the garbage collector and the manual memory management. Like the garbage collector, ARC eliminates the need for a developer to write retain / release / autorelease calls . However, unlike the garbage collector, ARC does not respond to retain cycles. Two objects with strict references to each other will never be processed by ARC, even if no one else refers to them. While ARC relieves the programmer of most memory management tasks, the developer still has to avoid or manually destroy the strict cyclic references to the object.

With regard to the specifics of implementation, there is another key difference between the ARC and the implementation of GC from Apple: ARC is not an improvement. When using Apple's GC, either the entire application works in the GC environment or not. This means that all Objective-C code in the application, including the Apple frameworks and third-party libraries used, must be compatible with the GC in order to use it. Notably, ARC peacefully coexists with “non-ARC” code with manual memory management in the same application. This makes it possible to convert piece projects without the major compatibility and reliability issues that GC encountered when it was implemented.

Xcode

ARC is available starting from Xcode 4.2 beta, and only when compiling with Clang is selected (aka "Apple LLVM compiler"). To use it, it is enough to note the setting with the obvious name “Objective-C Automatic Reference Counting”.
If you are already working with an existing code base, changing this setting may result in a huge number of errors. ARC not only manages memory for you, but also prohibits you from doing it yourself. It is completely unacceptable to send retain / release / autorelease messages to objects using ARC. Based on the fact that Cocoa is full of similar messages, you will inevitably get a ton of errors.
Fortunately, Xcode offers a tool for converting existing code. To do this, just select Edit -> Refactor ... -> Convert to Objective-C ARC ... - and Xcode will allow you to transform your code step by step. Excluding very narrow places where you will need to specify what to do, this process is largely automatic.

Basic functionality

The rules of memory management in Cocoa are quite simple. In short:
1. If you send an alloc, new, copy , or retain object, you must compensate for this using release or autorelease .
2. If you received an object in a different way, not described in item 1, and you need the object to be “alive” for a long time, you must use retain or copy . Naturally, this should be compensated by you later.

These (rules) are very helpful in automating the process. If you write:
Foo *foo = [[Foo alloc] init]; [foo something]; return; 


The compiler sees unbalanced alloc and converts to the following:
  Foo *foo = [[Foo alloc] init]; [foo something]; [foo release]; return; 


In fact, the compiler does not insert the code to send a release message to the object. Instead, it inserts a call to a special runtime function:
  Foo *foo = [[Foo alloc] init]; [foo something]; objc_release(foo); return; 

Here you can apply some optimization. In most cases where - release is not overridden, objc_release can bypass the Objective-C message mechanism, with the result that you can get a slight increase in speed.

This automation will help make the code safer. Many Cocoa developers have interpreted the expression “quite a long time” from clause 2, meaning objects stored in instance variables or similar places. Normally we do not apply retain and release to local temporary objects:
  Foo *foo = [self foo]; [foo bar]; [foo baz]; [foo quux]; 


However, the following construction is quite dangerous:
  Foo *foo = [self foo]; [foo bar]; [foo baz]; [self setFoo: newFoo]; [foo quux]; // crash 


This can be fixed by having a getter for - foo , in which retain / autorelease is sent before returning the value. It is a fully working version, but it can produce many temporary objects actively using memory. However, ARC paranoidly inserts extra calls here:
  Foo *foo = objc_retainAutoreleasedReturnValue([self foo]); [foo bar]; [foo baz]; [self setFoo: newFoo]; [foo quux]; // fine objc_release(foo); 


In addition, even if you wrote a simple getter, ARC will make it as safe as possible:
  - (Foo *)foo { return objc_retainAutoreleaseReturnValue(_foo); } 


However, this does not solve the problem of an excessive number of temporary objects completely! We, as before, use the retain / autorelease combination in the getter and the retain / release combination in the calling code. This is undoubtedly ineffective!

But do not worry without measure. As mentioned earlier, ARC (for optimization purposes) uses special calls instead of regular message sending. In addition to speeding up retain and release , these calls eliminate some operations altogether.

When objc_retainAutoreleaseReturnValue works, it monitors the stack and remembers the return address of the calling object. This allows him to know exactly what will happen after its completion. When compilation optimization is enabled, a call to objc_retainAutoreleaseReturnValue can be a reason to optimize tail recursion ( tail-call optimization ).

Rantaim uses this crazy return address check to avoid unnecessary work. This removes the autorelease sending and sets a flag that tells the caller to destroy the retain message. The whole construct uses a single retain in the getter and a single release in the calling code, which is a safer and more efficient approach.

Note: this optimization is fully compatible with code that does not use the ARC mechanism.
In an event whose getter does not use ARC, the above flag is not set and the caller can use the full combination of retain / release messages.
In an event whose getter uses ARC and the caller does not, the getter sees that it does not return to the code that immediately calls the special special runtime functions, and therefore can use the full combination of retain / autorelease messages.
Some performance is lost, but the code remains completely correct.

On top of that, the ARC mechanism automatically creates or populates the -dealloc method for all classes to free all of its instance variables. However, you still have the option of manually writing -dealloc (and this is necessary for classes that manage external resources), but in the future there is no need (or possibility) to manually release class instance variables. ARC will even put in a [super dealloc] call at the end, saving you from worrying about it.

We illustrate this.
Earlier you could have the following construction:
  - (void)dealloc { [ivar1 release]; [ivar2 release]; free(buffer); [super dealloc]; } 


But you just need to write:
  - (void)dealloc { free(buffer); } 


In the event that your -dealloc method simply releases the instance variables, this construct (using ARC) will do the same for you.

Cycles and weak links

ARC still requires the developer to manually handle circular references, and the best way to resolve this issue is to use weak references.

ARC uses zero weak links. Such links not only do not save the “live” object to which they refer, but also automatically become nil- s when the object to which they refer is destroyed. Resetting weak links allows you to fix the problem of frozen pointers and related crashes and unpredictable application behavior.
To create a nullable weak link, you just need to add the prefix __weak when you declare.
For example, here is the creation of an instance variable of this type:
  @interface Foo : NSObject { __weak Bar *_weakBar; } 


Local variables are created in the same way:
  __weak Foo *_weakFoo = [object foo]; 


You can later use them as ordinary variables, but their value will automatically become nil when the need arises:
  [_weakBar doSomethingIfStillAlive]; 


However, keep in mind that the __weak variable can become nil at almost any point in time.
Memory management is, in fact, a multi-threaded process and a loosely coupled object can be destroyed in one thread, while another thread is accessing it.
Illustrate.
The following code is not valid:
  if(_weakBar) [self mustNotBeNil: _weakBar]; 


Instead, use a local strong link and then check:
  Bar *bar = _weakBar; if(bar) [self mustNotBeNil: bar]; 


This is guaranteed to make the object alive (and the non- nil variable) for the entire time it is used, because in this case bar is a strict reference.

The implementation in ARC of zeroing out weak links requires close coordination between the reference accounting system in Objective-C and the system of zeroing out weak links. This means that the class that overrides retain and release cannot be an object of a nullable weak reference. Although unusual, some Cocoa classes suffer from this (eg, NSWindow ).

Fortunately, if you made a similar error, the program will let you know about it immediately, by issuing a message in the fall, in the manner of this:
  objc[2478]: cannot form weak reference to instance (0x10360f000) of class NSWindow 


If you really need a weak reference to these classes, you can use __unsafe_unretained instead of __weak . This will create a weak link that will NOT be reset. But you must be sure that you do not use this pointer (preferably, you manually reset it) AFTER the object it points to has been destroyed.
Be careful: using non-resettable weak links, you play with fire.

Despite the possibility of using ARC to write applications under Mac OS X 10.6 and iOS 4, nullable weak links are not available in these operating systems must be non-zeroing weak references).
All weak links should be processed with __unsafe_unretained .

Given that non-resettable weak links are so dangerous, this restriction greatly reduces the attractiveness of using ARC on these systems, as for me.



Here I will finish the first part of the translation, since the text has already turned out to be rather big. I tried to translate as best as possible into Russian, which is not particularly easy if 99% of the materials being read are in English.
As they say, stay tuned.

Source: https://habr.com/ru/post/129874/


All Articles