Advertisement

Objective-C Succinctly: Memory Management

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This post is part of a series called Objective-C Succinctly.
Objective-C Succinctly: Properties
Objective-C Succinctly: Methods

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.

Figure 19 Counting references to an object

Counting references to an object

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:

  1. Manually send methods to increment/decrement the number of references to an object.
  2. 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:

Figure 20 The HelloObjectiveC project in the navigation panel

The HelloObjectiveC project in the 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:

Figure 21 Disabling automatic reference counting

Disabling automatic reference counting

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.

Figure 22 Weak reference to the captain value

Weak reference to the captain value

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.

Figure 23 Strong reference to the captain value

Strong reference to the captain value

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 any retain / release calls. This is the automated equivalent of a weak reference.
  • retain - Store a direct pointer to the new value, but call release on the old value and retain 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 a release message. This is like a strong reference to a brand new instance of the object. Generally, copying is only used for immutable types like NSString.

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.

Figure 24 Enabling Automatic Reference Counting in the projects build settings

Enabling Automatic Reference Counting in the project's build settings

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 like assign; however, it has the convenient functionality of setting the property to nil 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 of retain. 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.
Advertisement