Memory must be allocated for each object your application uses, and it must be deallocated when your application is done with it to ensure your application is using memory as efficiently as possible. It's important to understand Objective-C's memory management environment to ensure your program doesn't leak memory or try to reference objects that no longer exist.
Unlike C#, Objective-C does not use garbage collection. Instead, it uses a reference-counting environment that tracks how many places are using an object. As long as there is at least one reference to the object, the Objective-C runtime makes sure the object will reside in memory. However, if there are no longer any references to the object, the runtime is allowed to release the object and use the memory for something else. If you try to access an object after it has been released, your program will most likely crash.
There are two mutually exclusive ways to manage object references in Objective-C:
- Manually send methods to increment/decrement the number of references to an object.
- Let Xcode 4.2's (and later) new automatic reference counting (ARC) scheme do the work for you.
ARC is the preferred way to manage memory in new applications, but it's still important to understand what's going on under the hood. The first part of this chapter shows you how to manually track object references, and then we'll talk about the practical implications of ARC.
Manual Memory Management
To experiment with any of the code in this section, you'll need to turn off automatic reference counting. You can do this by clicking the HelloObjectiveC project in Xcode's navigation panel:
This opens a window to let you adjust the build settings for the project. We'll discuss build settings in the second half of this series. For now, all we need to find is the ARC flag. In the search field in the upper-right corner, type automatic reference counting, and you should see the following setting appear:
Click the arrows next to Yes
and change it to No
to disable ARC for this project. This will let you use the memory management methods discussed in the following paragraphs.
Manual memory management (also called manual retain-release or MMR) revolves around the concept of object "ownership." When you create an object, you're said to own the object-it's your responsibility to free the object when you're done with it. This makes sense, since you wouldn't want some other object to come along and release the object while you're using it.
Object ownership is implemented through reference counting. When you claim ownership of an object, you increase its reference count by one, and when you relinquish ownership, you decrement its reference count by one. In this way, it's possible to ensure that an object will never be freed from memory while another object is using it. NSObject and the NSObject protocol define the four core methods that support object ownership:
-
+(id)alloc
- Allocate memory for a new instance and claim ownership of that instance. This increases the object's reference count by one. It returns a pointer to the allocated object. -
-(id)retain
- Claim ownership of an existing object. It's possible for an object to have more than one owner. This also increments the object's reference count. It returns a pointer to the existing object. -
-(void)release
- Relinquish ownership of an object. This decrements the object's reference count. -
-(id)autorelease
- Relinquish ownership of an object at the end of the current autorelease pool block. This decrements the object's reference count, but lets you keep using the object by deferring the actual release until a later point in time. It returns a pointer to the existing object.
For every alloc
or retain
method you call, you need to call release
or autorelease
at some point down the line. The number of times you claim an object must equal the number of times you release it. Calling an extra alloc
/retain
will result in a memory leak, and calling an extra release
/autorelease
will try to access an object that doesn't exist, causing your program to crash.
All of your object interactions-regardless of whether you're using them in an instance method, getter/setter, or a stand-alone function-should follow the claim/use/free pattern, as demonstrated in the following sample:
Included code sample: Manual Memory
int main(int argc, const char * argv[]) { // Claim the object. Person *frank = [[Person alloc] init]; // Use the object. frank.name = @"Frank"; NSLog(@"%@", frank.name); // Free the object. [frank release]; return 0; }
The [Person alloc]
call sets frank
's reference count to one, and [frank release]
decrements it to zero, allowing the runtime to dispose of it. Note that trying to call another [frank release]
would result in a crash, since the frank
variable no longer exists in memory.
When using objects as a local variable in a function (e.g., the previous example), memory management is pretty straightforward: simply call release
at the end of the function. However, things can get trickier when assigning properties inside of setter methods. For example, consider the following interface for a new class called Ship
:
Included code sample: Manual Memory - weak reference
// Ship.h #import "Person.h" @interface Ship : NSObject - (Person *)captain; - (void)setCaptain:(Person *)theCaptain; @end
This is a very simple class with manually defined accessor methods for a captain
property. From a memory-management perspective, there are several ways the setter can be implemented. First, take the simplest case where the new value is simply assigned to an instance variable:
// Ship.m #import "Ship.h" @implementation Ship { Person *_captain; } - (Person *)captain { return _captain; } - (void)setCaptain:(Person *)theCaptain { _captain = theCaptain; } @end
This creates a weak reference because the Ship
instance doesn't take ownership of the theCaptain
object when it gets assigned. While there's nothing wrong with this, and your code will still work, it's important to understand the implications of weak references. Consider the following snippet:
#import <Foundation/Foundation.h> #import "Person.h" #import "Ship.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *frank = [[Person alloc] init]; Ship *discoveryOne = [[Ship alloc] init]; frank.name = @"Frank"; [discoveryOne setCaptain:frank]; NSLog(@"%@", [discoveryOne captain].name); [frank release]; // [discoveryOne captain] is now invalid. NSLog(@"%@", [discoveryOne captain]. name); [discoveryOne release]; } return 0; }
Calling [frank release]
decrements frank
's reference count to zero, which means the runtime is allowed to deallocate it. This means that [discoveryOne captain]
now points to an invalid memory address, even though discoveryOne
never released it.
In the sample code provided, you will observe that we have added a dealloc
method override in the Person class. dealloc
is called when memory is about to be released. We should typically handle dealloc
and release any nested object references that we hold. In this instance we will release the nested name property that we hold. We will have more to say about dealloc
in the next chapter.
If you were to try to access the property, your program would most likely crash. As you can see, you need to be very careful tracking object references when you use weakly referenced properties.
For more robust object relationships, you can use strong references. These are created by claiming the object with a retain
call when it is assigned:
Included code sample: Manual Memory - strong reference
- (void)setCaptain:(Person *)theCaptain { [_captain autorelease]; _captain = [theCaptain retain]; }
With a strong reference, it doesn't matter what other objects are doing with the theCaptain
object, since retain
makes sure it will stay around as long as the Ship
instance needs it. Of course, you need to balance the retain
call by releasing the old value-if you didn't, your program would leak memory whenever anyone assigned a new value to the captain
property.
Auto-Releasing Objects
The autorelease
method works much like release
, except the object's reference count isn't decremented immediately. Instead, the runtime waits until the end of the current @autoreleasepool
block to call a normal release
on the object. This is why the main.m
template is always wrapped in an @autoreleasepool
-it makes sure all objects queued with autorelease
calls are actually released at the end of the program:
int main(int argc, const char * argv[]) { @autoreleasepool { // Insert code to create and autorelease objects here. NSLog(@"Hello, World!"); // Any autoreleased objects are *actually* released here. } return 0; }
The idea behind auto-releasing is to give an object's owner the ability to relinquish ownership without actually destroying the object. This is a necessary tool in situations where you need to return a new object from a factory method. For example, consider the following class method defined in Ship.m
:
+ (Ship *)shipWithCaptain:(Person *)theCaptian { Ship *theShip = [[Ship alloc] init]; [theShip setCaptain:theCaptian]; return theShip; }
This method creates, configures, and returns a new Ship
instance. But there's a serious problem with this implementation: it results in a memory leak. The method never relinquishes ownership of the object, and callers of shipWithCaptain
don't know that they need to free the returned object (nor should they have to). As a result, the theShip
object will never be released from memory. This is precisely the situation autorelease
was designed for. The proper implementation is shown here:
+ (Ship *)shipWithCaptain:(Person *)theCaptian { Ship *theShip = [[Ship alloc] init]; [theShip setCaptain:theCaptian]; return [theShip autorelease]; // Must relinquish ownership! }
Using autorelease
instead of an immediate release
lets the caller use the returned object while still relinquishing ownership of it in the proper location. If you remember from the Data Types chapter, we created all of our Foundation data structures using class-level factory methods. For example:
NSSet *crew = [NSSet setWithObjects:@"Dave", @"Heywood", @"Frank", @"HAL", nil];
The setWithObjects
method works exactly like the shipWithCaptain
method described in the previous example. It returns an autoreleased object so that the caller can use the object without worrying about memory management. Note that there are equivalent instance methods for initializing Foundation objects. For example, the crew
object in the last sample can be manually created as follows:
// Create and claim the set. NSSet *crew = [[NSSet alloc] initWithObjects:@"Dave", @"Heywood", @"Frank", @"HAL", nil]; // Use the set... // Release the set. [crew release];
However, using class methods like setWithObjects
, arrayWithCapacity
, etc., is generally preferred over the alloc
/init
.
Manual Retain-Release Attributes
Dealing with the memory behind an object's properties can be a tedious, repetitive task. To simplify the process, Objective-C includes several property attributes for automating the memory management calls in accessor functions. The attributes described in the following list define the setter behavior in manual reference-counting environments. Do not try to use assign
and retain
in an automatic reference counting environment.
-
assign
- Store a direct pointer to the new value without anyretain
/release
calls. This is the automated equivalent of a weak reference. -
retain
- Store a direct pointer to the new value, but callrelease
on the old value andretain
on the new one. This is the automated equivalent of a strong reference. -
copy
- Create a copy of the new value. Copying claims ownership of the new instance, so the previous value is sent arelease
message. This is like a strong reference to a brand new instance of the object. Generally, copying is only used for immutable types likeNSString
.
As a simple example, examine the following property declaration:
@property (retain) Person *captain;
The retain
attribute tells the associated @synthesize
declaration to create a setter that looks something like:
- (void)setCaptain:(Person *)theCaptain { [_captain release]; _captain = [theCaptain retain]; }
As you can imagine, using memory management attributes with @property
is much easier than manually defining getters and setters for every property of every custom class you define.
Automatic Reference Counting
Now that you've got a handle on reference counting, object ownership, and autorelease blocks, you can completely forget about all of it. As of Xcode 4.2 and iOS 4, Objective-C supports automatic reference counting (ARC), which is a pre-compilation step that adds in the necessary memory management calls for you.
If you happened to have turned off ARC in the previous section, you should turn it back on. Remember that you can do this by clicking on the HelloObjectiveC project in the navigation panel, selecting the Build Settings tab, and searching for automatic reference counting.
Automatic reference counting works by examining your code to figure out how long an object needs to stick around and inserting retain
, release
, and autorelease
methods to ensure it's deallocated when no longer needed, but not while you're using it. So as not to confuse the ARC algorithm, you must not make any retain
, release
, or autorelease
calls yourself. For example, with ARC, you can write the following method and neither theShip
nor theCaptain
will be leaked, even though we didn't explicitly relinquish ownership of them:
Included code sample: ARC
+ (Ship *)ship { Ship *theShip = [[Ship alloc] init]; Person *theCaptain = [[Person alloc] init]; [theShip setCaptain:theCaptain]; return theShip; }
ARC Attributes
In an ARC environment, you should no longer use the assign
and retain
property attributes. Instead, you should use the weak
and strong
attributes:
-
weak
- Specify a non-owning relationship to the destination object. This is much likeassign
; however, it has the convenient functionality of setting the property tonil
if the value is deallocated. This way, your program won't crash when it tries to access an invalid memory address. -
strong
- Specify an owning relationship to the destination object. This is the ARC equivalent ofretain
. It ensures that an object won't be released as long as it's assigned to the property.
You can see the difference between weak and strong using the implementation of the ship
class method from the previous section. To create a strong reference to the ship's captain, the interface for Ship
should look like the following:
// Ship.h #import "Person.h" @interface Ship : NSObject @property (strong) Person *captain; + (Ship *)ship; @end
And the implementation Ship
should look like:
// Ship.m #import "Ship.h" @implementation Ship @synthesize captain = _captain; + (Ship *)ship { Ship *theShip = [[Ship alloc] init]; Person *theCaptain = [[Person alloc] init]; [theShip setCaptain:theCaptain]; return theShip; } @end
Then, you can change main.m
to display the ship's captain:
int main(int argc, const char * argv[]) { @autoreleasepool { Ship *ship = [Ship ship]; NSLog(@"%@", [ship captain]); } return 0; }
This will output something like <Person: 0x7fd6c8c14560>
in the console, which tells us that the theCaptain
object created in the ship
class method still exists.
But, try changing the (strong)
property attribute to (weak)
and re-compiling the program. Now, you should see (null)
in the output panel. The weak reference doesn't ensure that the theCaptain
variable sticks around, so once it arrives at the end of the ship
class method, the ARC algorithm thinks that it can dispose of theCaptain
. As a result, the captain
property is set to nil
.
Summary
Memory management can be a pain, but it's an essential part of building an application. For iOS applications, proper object allocation/disposal is particularly important because of the limited memory resources of mobile devices. We'll talk more about this in the second part of this series, iOS Succinctly.
Fortunately, the new ARC scheme makes memory management much easier on the average developer. In most cases, it's possible to treat an ARC project just like the garbage collection in a C# program-just create your objects and let ARC dispose of them at its discretion. Note, however, that this is merely a practical similarity-the ARC implementation is much more efficient than garbage collection.
This lesson represents a chapter from Objective-C Succinctly, a free eBook from the team at Syncfusion.
Comments