Automatic Reference Counting: Part 1
- Transfer
Hello colleagues.
I have been reading blogs and articles of foreign developers for iOS for a long time. And the other day I came across a curious, rather detailed article about Automatic Reference Counting from a developer named Mike Ash .
The article is quite large, therefore I will risk dividing the translation made by me into several parts. I hope that I will keep 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 we’ll talk about Apple’s new memory management system: how it works and how to make it work for yourself.
Concept
Clang Static Analyzer is a really useful tool for finding memory management errors in your code. If you and I are alike, then, looking at the analyzer’s output, we think: “If the analyzer detected the error, why shouldn’t it be corrected 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 find errors, the ARC mechanism simply inserts the necessary calls. And that’s all.
The ARC mechanism is somewhere between the garbage collector and manual memory management. Like the garbage collector, ARC eliminates the need for developers to write retain / release / autorelease calls . However, unlike the garbage collector, ARC does not react in any way to retain loops.. Two objects with strong links 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 needs to avoid or manually destroy strict circular references to the object.
In terms of implementation specifics, that’s another key difference between ARC and Apple’s GC implementation: ARC is not an improvement. When using Apple’s GC, either the entire application runs in the GC environment or not. This means that all Objective-C code in the application, including the used Apple frameworks and third-party libraries, must be compatible with GC in order to use it. It is noteworthy that ARC coexists peacefully with “non-ARC” code with manual memory management in the same application. This makes it possible to convert projects in parts without the big compatibility and reliability issues that GC encountered when it was introduced.
Xcode
ARC is available starting with Xcode 4.2 beta, and only when choosing compilation using Clang (aka "Apple LLVM compiler"). To use it, it’s 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 can lead to a huge number of errors. ARC not only manages memory in your place, but also prohibits you from doing it yourself. Using ARC is completely unacceptable to send retain / release / autorelease messages to objects. Based on the fact that Cocoa is full of such messages, you will inevitably get a ton of errors.
Fortunately, Xcode offers a tool to convert existing code. To do this, just selectEdit -> Refactor ... -> Convert to Objective-C ARC ... - and Xcode allows you to step-by-step convert your code. Excluding very bottlenecks where you will need to specify what to do, this process is largely automatic.
Basic functionality
Cocoa's memory management rules are fairly straightforward. In short:
1. If you send a alloc, new, copy , or retain to an object, you must compensate for this using release or autorelease .
2. If you received the object in a different way than described in paragraph 1, and you need the object to be “alive” for a sufficiently long time, you must use retain or copy. Naturally, this should be compensated by you later.
They (the rules) greatly contribute to the automation of the process. If you write:
The compiler sees an unbalanced alloc and converts to the following:
In reality, the compiler does not insert the code for sending the release message to the object. Instead, it inserts a call to a special runtime function:
Here you can apply some optimization. In most cases when - release is not overridden, objc_release can bypass the Objective-C message mechanism, resulting in a slight increase in speed.
Such automation will help make the code safer. Many Cocoa developers interpret the term “quite a long time” from clause 2, implying objects stored in instance variables or similar places. Usually we do not apply retain and release to local temporary objects:
However, the following construction is quite dangerous:
This can be fixed by having a getter for - foo , in which retain / autorelease is sent until the value is returned. It is a working option, but can produce many temporary objects that actively use memory. However, ARC will paranoid insert extra calls here:
In addition, even if you wrote a simple getter, ARC will make it as safe as possible:
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 just sending messages. In addition to accelerating retain and release , these calls eliminate some operations in general.
When objc_retainAutoreleaseReturnValueworks, it monitors the stack and remembers the return address of the caller. This allows him to know exactly what will happen after its completion. With compilation optimization turned on, calling objc_retainAutoreleaseReturnValue may be the reason for tail-call optimization .
Rantime uses this crazy check of return address to avoid unnecessary work. This eliminates the autorelease sending and sets a flag that tells the caller to destroy the retain message. The whole construction in total uses a single retain in the getter and a single releasein 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, but the caller does not, the getter sees that it does not return to the code that immediately calls special special runtime functions, and therefore can use the full combination of retain / autorelease messages.
Some performance is lost, but the complete correctness of the code is preserved.
On top of that, the ARC engine automatically creates or populates the -dealloc method for all classes to free all instance variables. At the same time, there is still the possibility 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 insert a [super dealloc] call at the end, saving you from worrying about it.
We illustrate what has been said.
Previously, you could have the following construction:
But you just need to write:
In case your -dealloc method just freed instance variables, this construct (using ARC) will do the same for you.
Loops and weak links
ARC still requires the developer to manually handle circular links, and the best way to resolve this issue is to use weak links.
ARC uses nulling weak links. Such links not only do not keep the object to which they refer “live”, but also automatically become nil when the object to which they refer is destroyed. Zeroing weak links eliminates the problem of dead pointers and the associated crashes and unpredictable behavior of the application.
To create a nullable weak link, you just need to add the prefix__weak at the announcement.
For example, here is the creation of an instance variable of this type:
Local variables are created in the same way:
You can later use them as regular variables, but their value will automatically become nil when the need arises:
However, keep in mind that the __weak variable can become nil at almost any point in time.
Memory management, in fact, is a multi-threaded process and a loosely coupled object can be destroyed in one thread, while another thread accesses it.
We will illustrate.
The following code is incorrect:
Instead, use a local strong link and then check:
This is guaranteed to make the object live (and the non- nil variable ) for the entire time it is used, because in this case bar is a strong reference.
Implementation in ARC of nulling weak links requires close coordination between the reference accounting system in Objective-C and the nulling system of weak links. This means that a class that overrides retain and release cannot be an object of a nullable weak reference. Although unusual, some Cocoa classes suffer from this (e.g. NSWindow ).
Fortunately, if you make a similar mistake, the program will let you know about it immediately by issuing a message when it crashes, in the manner of this:
If you really need a weak reference to similar 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 are not using this pointer (it is preferable that you reset it manually) AFTER the object that it points to has been destroyed.
Be careful: using non-nullable weak links, you play with fire.
Despite the possibility of using ARC to write applications for Mac OS X 10.6 and iOS 4, nullable weak links are not available on these OSs.(Translator's note: I completely did not understand the meaning of this passage in the context of the ARC parsing. Perhaps the author made a typo, instead of zeroing weak references there should be non-zeroing weak references).
All weak links should be processed with __unsafe_unretained .
Given that nullable weak links are so dangerous, this limitation 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 turned out rather big. I tried to translate it into Russian as best as possible, which is not particularly simple if 99% of the materials read are in English.
As they say, stay tuned.
I have been reading blogs and articles of foreign developers for iOS for a long time. And the other day I came across a curious, rather detailed article about Automatic Reference Counting from a developer named Mike Ash .
The article is quite large, therefore I will risk dividing the translation made by me into several parts. I hope that I will keep 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 we’ll talk about Apple’s new memory management system: how it works and how to make it work for yourself.
Concept
Clang Static Analyzer is a really useful tool for finding memory management errors in your code. If you and I are alike, then, looking at the analyzer’s output, we think: “If the analyzer detected the error, why shouldn’t it be corrected 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 find errors, the ARC mechanism simply inserts the necessary calls. And that’s all.
The ARC mechanism is somewhere between the garbage collector and manual memory management. Like the garbage collector, ARC eliminates the need for developers to write retain / release / autorelease calls . However, unlike the garbage collector, ARC does not react in any way to retain loops.. Two objects with strong links 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 needs to avoid or manually destroy strict circular references to the object.
In terms of implementation specifics, that’s another key difference between ARC and Apple’s GC implementation: ARC is not an improvement. When using Apple’s GC, either the entire application runs in the GC environment or not. This means that all Objective-C code in the application, including the used Apple frameworks and third-party libraries, must be compatible with GC in order to use it. It is noteworthy that ARC coexists peacefully with “non-ARC” code with manual memory management in the same application. This makes it possible to convert projects in parts without the big compatibility and reliability issues that GC encountered when it was introduced.
Xcode
ARC is available starting with Xcode 4.2 beta, and only when choosing compilation using Clang (aka "Apple LLVM compiler"). To use it, it’s 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 can lead to a huge number of errors. ARC not only manages memory in your place, but also prohibits you from doing it yourself. Using ARC is completely unacceptable to send retain / release / autorelease messages to objects. Based on the fact that Cocoa is full of such messages, you will inevitably get a ton of errors.
Fortunately, Xcode offers a tool to convert existing code. To do this, just selectEdit -> Refactor ... -> Convert to Objective-C ARC ... - and Xcode allows you to step-by-step convert your code. Excluding very bottlenecks where you will need to specify what to do, this process is largely automatic.
Basic functionality
Cocoa's memory management rules are fairly straightforward. In short:
1. If you send a alloc, new, copy , or retain to an object, you must compensate for this using release or autorelease .
2. If you received the object in a different way than described in paragraph 1, and you need the object to be “alive” for a sufficiently long time, you must use retain or copy. Naturally, this should be compensated by you later.
They (the rules) greatly contribute to the automation of the process. If you write:
Foo *foo = [[Foo alloc] init];
[foo something];
return;
The compiler sees an unbalanced alloc and converts to the following:
Foo *foo = [[Foo alloc] init];
[foo something];
[foo release];
return;
In reality, the compiler does not insert the code for sending the 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 when - release is not overridden, objc_release can bypass the Objective-C message mechanism, resulting in a slight increase in speed.
Such automation will help make the code safer. Many Cocoa developers interpret the term “quite a long time” from clause 2, implying objects stored in instance variables or similar places. Usually 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 until the value is returned. It is a working option, but can produce many temporary objects that actively use memory. However, ARC will paranoid insert 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 just sending messages. In addition to accelerating retain and release , these calls eliminate some operations in general.
When objc_retainAutoreleaseReturnValueworks, it monitors the stack and remembers the return address of the caller. This allows him to know exactly what will happen after its completion. With compilation optimization turned on, calling objc_retainAutoreleaseReturnValue may be the reason for tail-call optimization .
Rantime uses this crazy check of return address to avoid unnecessary work. This eliminates the autorelease sending and sets a flag that tells the caller to destroy the retain message. The whole construction in total uses a single retain in the getter and a single releasein 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, but the caller does not, the getter sees that it does not return to the code that immediately calls special special runtime functions, and therefore can use the full combination of retain / autorelease messages.
Some performance is lost, but the complete correctness of the code is preserved.
On top of that, the ARC engine automatically creates or populates the -dealloc method for all classes to free all instance variables. At the same time, there is still the possibility 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 insert a [super dealloc] call at the end, saving you from worrying about it.
We illustrate what has been said.
Previously, 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 case your -dealloc method just freed instance variables, this construct (using ARC) will do the same for you.
Loops and weak links
ARC still requires the developer to manually handle circular links, and the best way to resolve this issue is to use weak links.
ARC uses nulling weak links. Such links not only do not keep the object to which they refer “live”, but also automatically become nil when the object to which they refer is destroyed. Zeroing weak links eliminates the problem of dead pointers and the associated crashes and unpredictable behavior of the application.
To create a nullable weak link, you just need to add the prefix__weak at the announcement.
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 regular 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, in fact, is a multi-threaded process and a loosely coupled object can be destroyed in one thread, while another thread accesses it.
We will illustrate.
The following code is incorrect:
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 live (and the non- nil variable ) for the entire time it is used, because in this case bar is a strong reference.
Implementation in ARC of nulling weak links requires close coordination between the reference accounting system in Objective-C and the nulling system of weak links. This means that a class that overrides retain and release cannot be an object of a nullable weak reference. Although unusual, some Cocoa classes suffer from this (e.g. NSWindow ).
Fortunately, if you make a similar mistake, the program will let you know about it immediately by issuing a message when it crashes, 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 similar 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 are not using this pointer (it is preferable that you reset it manually) AFTER the object that it points to has been destroyed.
Be careful: using non-nullable weak links, you play with fire.
Despite the possibility of using ARC to write applications for Mac OS X 10.6 and iOS 4, nullable weak links are not available on these OSs.(Translator's note: I completely did not understand the meaning of this passage in the context of the ARC parsing. Perhaps the author made a typo, instead of zeroing weak references there should be non-zeroing weak references).
All weak links should be processed with __unsafe_unretained .
Given that nullable weak links are so dangerous, this limitation 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 turned out rather big. I tried to translate it into Russian as best as possible, which is not particularly simple if 99% of the materials read are in English.
As they say, stay tuned.