Chapter     4

Memory Management

So far, you’ve learned how to develop classes and create and initialize objects. Now is a great time to study the details of Objective-C memory management. Proper memory management is key to developing programs that perform both correctly and efficiently. In this chapter, you’ll look into how computer memory is allocated and released for Objective-C programs, the Objective-C memory model, and how to write programs that perform memory management properly.

The overall quality of a program is often directly related to its management of system resources. A computer’s operating system allocates a finite amount of main memory for program execution, and if a program attempts to use more memory than the amount allocated by the O/S, it will not operate correctly. Hence, a program should use only as much memory as needed, neither allocating memory that it does not use nor trying to use memory that is no longer available. The Objective-C language and runtime provide mechanisms to support application memory management in alignment with these goals.

Program Memory Usage

In order to better understand the role of memory management, you need to understand how a program is stored and used in computer memory. An executable Objective-C program is composed of (executable) code, initialized and unitialized program data, linking information, relocation information, local data, and dynamic data. The program data includes statically-declared variables and program literals (e.g., constant values in the code that are set during program compilation).

The executable code and program data, along with the linking and relocation information, are statically allocated in memory and persist for the lifetime of the program.

Local (i.e., automatic) data consists of data whose scope is delimited by the statement block within which it is declared and is not retained after the block has been executed. Syntactically, an Objective-C compound statement block is a collection of statements delimited by braces (as shown in Listing 4-1).

Listing 4-1.  Example Statement Block

{
  int myInt = 1;
  float myFloat = 5.0f;
  NSLog(@"Float = %f, integer =  %d", myFloat, myInt);
}

The example in Listing 4-1 includes two local variables, myInt and myFloat, which exist within this statement block and are not retained after the block has been executed. Automatic data resides in the program stack, a block of memory whose size is usually set prior to program/thread execution. The stack is used to store local variables and context data for method/function invocation. This context data consists of method input parameters, return values, and the return address where the program should continue execution after the method completes. The operating system automatically manages this memory; it is allocated on the stack for and subsequently deallocated at the end of the scope where it was declared.

At runtime, an Objective-C program creates objects (via the NSObject alloc method, as discussed in Chapter 3) stored in dynamically allocated memory referred to as the program heap. Dynamic object creation implies the need for memory management, because objects created on the heap never go out of scope.

A diagram depicting the allocation and usage of memory during program execution is shown in Figure 4-1.

9781430250500_Fig04-01.jpg

Figure 4-1. Objective-C program memory usage

The memory allocated for the program code is set at compilation time and, hence, accounted for in the overall amount of system memory used. The memory allocated on the program stack (usually) has a maximum size determined at program startup, and is automatically managed by the O/S. On the other hand, Objective-C objects are dynamically created during program execution and are not automatically reclaimed by the O/S. Thus, Objective-C programs require memory management to ensure proper utilization of system memory. Typical results of no or incorrect program memory management include the following:

  • Memory leaks: These are caused when a program does not free unused objects. If a program allocates memory that it doesn’t use, memory resources are wasted; if it continually allocates memory without releasing it, the program will eventually run out of memory.
  • Dangling pointers: These are caused when a program frees objects still being used. This condition results in a dangling pointer; if the program attempts to access these objects later, a program error will result.

Objective-C Memory Model

Objective-C memory management is implemented using reference counting, a technique whereby unique references to an object are used to determine whether or not an object is still in use. If an object’s reference count drops to zero, it is considered no longer in use and its memory will be deallocated by the runtime system.

The Apple Objective-C development environment (for both the OS X and iOS platforms) provides two mechanisms that can be used for memory management: Manual Retain-Release and Automatic Reference Counting. In the next few paragraphs, you’ll learn how these work and you’ll develop a few programs to become familiar with their usage.

Implementing Manual Retain-Release

Manual Retain-Release (MRR) is a memory-management mechanism based on the concept of object ownership: as long as there are one or more owners of an object, it will not be deallocated by the Objective-C runtime. You write your code to explicitly manage the life cycle of objects, obtaining an ownership interest in objects that you create/need to use, and releasing that ownership interest when you no longer need the object(s). To see how this is applied, first you need to understand how objects are accessed and used, and the difference between object access and ownership.

Object Reference and Object Ownership

An Objective-C object is accessed by indirection through a variable that points to the memory address of the Objective-C object. This variable, also called a pointer, is declared by prefixing its name with an asterisk. The following statement

Hydrogen *atom;

declares a pointer variable named atom that points to an object of type Hydrogen. Note that pointers and indirection can be used for any Objective-C data type, including primitives and C data types; however, object pointers are used specifically for interacting with Objective-C objects.

An object pointer enables access to an Objective-C object, but does not by itself manage ownership. Look at the following code:

Hydrogen *atom = [Hydrogen init];
...
Hydrogen *href = atom;

The pointer href is declared and assigned to a Hydrogen object named atom, but href does not (by assignment) claim an ownership interest in this object. Hence, if atom is deallocated, href will no longer point to a valid object. Thus, to manage the life cycle (i.e., the existence) of an object using MRR, your code must observe a set of memory management rules. You’ll look at these next.

Basic Memory Management Rules

To use MRR correctly, your code needs to balance ownership claims on an object with those releasing ownership claims on the object. In order to support this requirement, you should write your code observing the MRR basic memory-management rules, as follows:

  • Claim ownership interest in any object that you create. You create an Objective-C object with a method whose name begins with alloc, new, copy, or mutableCopy. You also dynamically create an Objective-C block object by sending the copy message to the block.
  • Take ownership interest in an object (that you don’t already own) using the retain method. NSObject implements a retain method that is used to take ownership interest in an object. Under normal circumstances, an object is guaranteed to remain valid both within the method it is received (as a parameter), and as a return value. The retain method is used to take ownership of an object that you want to use over a long period of time, typically to store it as a property value, or to prevent it from being deallocated as a side effect of some other operation.
  • You must relinquish ownership of objects you own when no longer needed. NSObject implements the release and autorelease methods that are used to relinquish ownership interest in an object. The autorelease method relinquishes ownership interest of an object at the end of the current autorelease block. The release method immediately relinquishes ownership interest in an object. Both methods send the dealloc method on an object if its retain count equals zero.
  • You must not relinquish ownership of objects you do not own. This could cause such objects to be released prematurely. If a program attempts to access an object that has already been deallocated, it will cause an error.

Listing 4-2 shows an example that illustrates application of the MRR basic memory-management rules.

Listing 4-2.  MRR Example Usage

Hydrogen *atom = [Hydrogen init];
...
Hydrogen *href = [atom retain];
...
[atom release];
...
[href release];

A Hydrogen object is created and assigned to a variable named atom. Since the object is created with an alloc message, atom has an ownership interest in this object. Later in the code, a variable named href takes an ownership interest in this object, thereby increasing the retain count (i.e., the number of ownership interests) in the object. Later, a release message is sent to the atom variable. As href still has an ownership interest in the object, it is not deallocated. Once href sends a release message to the object, its retain count drops to zero and it can be deallocated by the runtime.

Deallocating Memory

When an object’s retain count drops to zero, the runtime frees the memory it uses via the NSObject dealloc method. This method also provides a framework for your subclasses to relinquish ownership of any objects it owns. Each of your classes (which nominally descend from NSObject) should override the dealloc method, invoking the release method on each object it has an ownership interest in, and then call the dealloc method of its superclass. This approach enables each of your classes to properly relinquish ownership of any objects it owns throughout its class hierarchy, as shown in Figure 4-2.

9781430250500_Fig04-02.jpg

Figure 4-2. A dealloc method implementation

Delayed Release via Autorelease

NSObject provides an autorelease method that is used to invoke the release method on an object at the end of an autorelease pool block. Autorelease pool blocks provide a mechanism that enables you to relinquish ownership of an object at some time in the future, thereby eliminating the need to explicitly invoke the release method on the object and also avoiding the possibility of it being deallocated immediately. Autorelease pool blocks are defined using the @autorelease directive, as shown in Listing 4-3.

Listing 4-3.  Autorelease Pool Block Syntax

@autorelease
{
  // Code that creates autoreleased objects
  ...
}

Autoreleased objects should always be coded within an autorelease pool block, otherwise they will not receive release messages and would therefore leak memory. The Apple UI frameworks used to create iOS and Mac OS X apps (specifically AppKit and UI Kit) provide autorelease blocks automatically; hence, you would normally only code an autorelease block if

  • You are writing a program that is not based on a UI framework, such as a command-line tool.
  • Your implementation logic includes a loop that creates many temporary objects. In order to reduce the maximum memory footprint of an application, you may use an autorelease pool block inside a loop to dispose of those objects before the next iteration.
  • Your application spawns one or more secondary threads. You must create your own autorelease pool block as soon as a thread begins executing; otherwise, your application will leak memory.

In the example shown in Listing 4-4, the autorelease block surrounds all the code within the main() function, and an object is dynamically allocated with the autorelease message.

Listing 4-4.  Command-Line Program with Autorelease Block

int main(int argc, const char * argv[])
{
  @autorelease
  {
    Hydrogen *atom = [[[Hydrogen alloc] init] autorelease];
    ...
  }
  return 0;
}

Note that the autorelease message is sent immediately after the object has been created and initialized, usually in a single, combined statement. This design ensures that any objects created with autorelease will be released at the end of the autorelease block, prior to program end.

Using MRR

Now that you’ve gone over Objective-C memory management and MRR, it’s time to apply what you’ve learned by developing an example program! This example will demonstrate the application of the MRR basic memory-management rules, along with usage of the corresponding APIs. Briefly, the program consists of three classes: an OrderEntry class used to record product orders, and the dependent classes OrderItem and Address. The OrderItem class encapsulates the state and behaviors specific to an order entry item, and the Address class consists of the state and behavior associated with an order entry shipping address. Each OrderEntry object has a corresponding OrderItem object and Address object (as shown in Figure 4-3).

9781430250500_Fig04-03.jpg

Figure 4-3. Order entry classes

You will develop these classes using MRR memory management, and then test them to demonstrate proper use of this memory-management technique. In Xcode, create a new project by selecting New image Project ... from the Xcode File menu. In the New Project Assistant pane, create a command-line application (choose Command Line Tool from the Mac OS X Application selection) and click Next. In the Project Options window, specify MRR Orders for the Product Name, set the Type to Foundation, select MRR memory management by unchecking the Use Automatic Reference Counting check box (as shown in Figure 4-4), and then click Next.

9781430250500_Fig04-04.jpg

Figure 4-4. Creating an MRR order entry project

Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Now let’s add the OrderEntry, OrderItem, and Address classes. As you’ve done in previous chapters, add each class to the project by selecting New image File ... from the Xcode File menu, selecting the Objective-C class template, naming the class appropriately (each is a subclass of NSObject), then selecting the MRR Orders folder for the files location and the MRR Orders project as the target.

Your project now has files for three classes along with the main.m file. Next, let’s implement the Address class. You are not going to add any instance variables, properties, or methods to the class, so you won’t be editing the Address interface. However, you will edit the Address implementation by overriding the default init and dealloc methods provided by its superclass (NSObject). Select the Address.m file in the navigator pane, and then add the code shown in Listing 4-5.

Listing 4-5.   MRR Address Class Implementation

#import "Address.h"

@implementation Address
- (id) init
{
  if ((self = [super init]))
  {
    NSLog(@"Initializing Address object");
  }
  return self;
}

- (void)dealloc
{
  NSLog(@"Deallocating Address object");
  [super dealloc];
}
@end

As you can see in Listing 4-5, the init and dealloc methods each log a message to the console to show method entry. When you test the program, this enables you to see when the runtime invokes each of these methods. In addition, the dealloc method invokes dealloc on its superclass, thereby ensuring the object’s memory is reclaimed. OK, you’re done with the Address class; now let’s implement the OrderItem class.

Select the OrderItem.h file in the navigator pane, and then add the code shown in Listing 4-6.

Listing 4-6.  MRR OrderItem Class Interface

#import <Foundation/Foundation.h>

@interface OrderItem : NSObject
{
@public NSString *name;
}

- (id) initWithName:(NSString *)itemName;
@end

The OrderItem class adds a public instance variable named name of type (NSString *), and a custom initializer (initWithName:) that takes an input parameter also of type (NSString *). Next, select the OrderItem.m file and implement the OrderItem class, as shown in Listing 4-7.

Listing 4-7.  MRR OrderItem Class Implementation

#import "OrderItem.h"

@implementation OrderItem
- (id) initWithName:(NSString *)itemName
{
  if ((self = [super init]))
  {
    NSLog(@"Initializing OrderItem object");
    name = itemName;
    [name retain];
  }
  return self;
}

- (void)dealloc
{
  NSLog(@"Deallocating OrderItem object");
  [name release];
  [super dealloc];
}
@end

The custom initializer assigns the instance variable to the input parameter. Since the OrderItem object did not create the input parameter, it doesn’t have an ownership interest in it. However, as you don’t want this object to be deallocated as a side effect of some other operation (and also require it to be valid throughout the object’s lifetime), you send a retain message to the name variable, thereby taking an ownership interest in the object. In the dealloc method, you send a release message to the name object, then invoke dealloc on its superclass to relinquish the OrderItem object’s memory.

Now let’s implement the OrderEntry class. Select the OrderEntry.h file and code the interface (as shown in Listing 4-8).

Listing 4-8.  MRR OrderEntry Class Interface

#import <Foundation/Foundation.h>
#import "OrderItem.h"
#import "Address.h"

@interface OrderEntry : NSObject
{
@public OrderItem *item;
NSString *orderId;
Address *shippingAddress;
}

- (id) initWithId:(NSString *)oid;
@end

Listing 4-8 shows that an OrderEntry object has three instance variables and a custom initializer (initWithId:). Next, select the OrderEntry.m file and implement the OrderEntry class, as shown in Listing 4-9.

Listing 4-9.  MRR OrderEntry Class Implementation

#import "OrderEntry.h"

@implementation OrderEntry
- (id) initWithId:(NSString *)oid
{
  if ((self = [super init]))
  {
    NSLog(@"Initializing OrderEntry object");
    orderId = oid;
    [orderId retain];
    item = [[OrderItem alloc] initWithName:@"Doodle"];
    shippingAddress = [[Address alloc] init];
  }
  
  return self;
}

- (void)dealloc
{
  NSLog(@"Deallocating OrderEntry object");
  [item release];
  [orderId release];
  [shippingAddress release];
  [super dealloc];
}
@end

The custom initializer assigns the input parameter to the orderId instance variable, and then creates and initializes OrderItem and Address objects. As the OrderEntry object has an ownership interest in the OrderItem and Address objects, and also the orderId instance, it is responsible for releasing them prior to it being deallocated. This is accomplished in the dealloc method, which then invokes dealloc on its superclass to relinquish the OrderEntry object’s memory.

So you’ve implemented the order entry classes, now test the classes to validate the implementation and also verify that you used MRR correctly. Select the main.m file in the navigator pane, and then update the main() function as shown in Listing 4-10.

Listing 4-10.  MRR main() Function Implementation

#import <Foundation/Foundation.h>
#import "OrderEntry.h"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Create an OrderEntry object for manual release
    NSString *orderId = [[NSString alloc] initWithString:@"A1"];
    OrderEntry *entry = [[OrderEntry alloc] initWithId:orderId];

    // Release orderId (retained by OrderEntry, so object not deallocated!)
    [orderId release];
    NSLog(@"New order, ID = %@, item: %@", entry->orderId, entry->item->name);
    
    // Must manually release OrderEntry!
    [entry release];
    
    // Create an autorelease OrderEntry object, released at the end of
    // the autorelease pool block
    OrderEntry *autoEntry = [[[OrderEntry alloc] initWithId:@"A2"] autorelease];
    NSLog(@"New order, ID = %@, item: %@", autoEntry->orderId, autoEntry->item->name);
  }
  return 0;
}

The main() function demonstrates MRR memory management by creating OrderEntry objects and releasing them, along with the objects it has an ownership interest in, according to the memory-management rules. The first OrderEntry object is manually released, while the second is created with autorelease, and thus is released at the end of its surrounding autorelease pool block.

Now save, compile, and run the MRR Orders program and observe the messages in the output pane (as shown in Figure 4-5).

9781430250500_Fig04-05.jpg

Figure 4-5. Testing the MRR OrderEntry project

The messages show the sequence of calls to initialize and deallocate the dynamically created objects. It looks like all the object create/retain and release messages are correctly balanced, and the tests you ran displayed the appropriate output, so you’re all done, right? Well, not quite. After all, how do you know for sure that the program doesn’t have a memory leak—it could display the correct output but still have an undetected problem. So, how do you verify that the program isn’t leaking memory or has some other undetected problem? Xcode has a suite of tools that can be used to analyze your program and detect potential problems. In Appendix B, you’ll learn how to use the Xcode Instruments tool to do just that, but here you’ll use Xcode’s Build and Analyze tool to perform an initial check of the application.

Select Analyze from the Xcode Product menu. The tool performs an analysis of the program, detecting, among other things, any potential memory leaks or dangling pointers. If an error(s) is detected, it identifies the error. Now observe the Activity View in the middle of the toolbar; it should display “Analyze Succeeded,” meaning that no memory-management errors were detected. In the main() function, comment-out the [orderId release] statement, and then run the Build and Analyze tool again. It should display the message shown in Figure 4-6.

9781430250500_Fig04-06.jpg

Figure 4-6. Analyzing MRR for the OrderEntry project

As shown in Figure 4-6, when a dynamically allocated object (orderId in this case) does not have a balanced set of retain/release messages, a potential memory leak is detected. This demonstrates that the Build and Analyze tool is great for performing an initial first-check on program memory management. Now, go ahead and remove the comment markers from your program—you’ve proven that it manages memory correctly using MRR. Great job!

Using Automatic Reference Counting

With MRR memory management, you are directly responsible for managing the life cycle of your program’s objects. This enables fine-grain control of memory usage, at the cost of placing a significant burden on the programmer. Even with the simple example you just implemented, it requires a lot of diligence (and testing with the proper tools) to make sure that you are balancing your retains and releases properly and in a timely manner. In general, manual reference counting is an error-prone task, which, if not done correctly, can cause a program to leak memory and/or crash. In fact, this type of infrastructure is best relegated to a tool, thereby freeing programmers to focus on the actual business logic your programs are designed to implement. Enter Automatic Reference Counting (ARC), a memory-management enhancement that automates this task, thereby eliminating the responsibility for the programmer. ARC employs the same reference counting model as MRR, but utilizes the compiler to manage the object life cycle. Specifically, at program compilation, the compiler analyzes the source code, determines the life cycle requirements for dynamically created objects, and then automatically inserts retain and release messages where necessary in the compiled code. ARC provides other benefits as well: application performance is (potentially) improved and errors associated with memory management (e.g., releasing an object that is still in use, retaining an object no longer in use) are eliminated. In addition, as opposed to garbage collection technology, ARC is deterministic (the statements are inserted at compile time) and doesn’t introduce pauses into program execution for garbage collection.

ARC provides automatic memory management for Objective-C objects and block objects. It is the recommended approach for memory management in new Objective-C projects. ARC can be used across a project or on a per-file basis if there is a need for manual reference counting in some cases. Note that ARC does not automatically handle reference cycles, where objects have circular references. Objective-C provides language features for declaring weak references on objects as necessary to manually break these cycles. You’ll look at these later in this chapter.

Rules and Conventions for Using ARC

As mentioned, ARC greatly simplifies memory management with respect to the MRR method. As a result, there are different sets of rules for object memory management under ARC. These rules are as follows:

  • You cannot send retain, retainCount, release, autorelease, or dealloc messages. ARC forbids programmatic control of an object life cycle and automatically inserts these messages where required during compilation. This includes @selector(retain), @selector(release), and related methods. You may implement a dealloc method if you need to manage resources other than instance variables. ARC automatically creates a dealloc method in your classes (if not provided) to release the objects it owns, and invokes [super dealloc] in your implementation(s) of the dealloc method.
  • You can’t cast directly between id and (void *) types. ARC only manages Objective-C objects and blocks, thus the compiler needs to know the types of the objects it is processing. Because a pointer to void (void *) is a generic pointer type that can be converted to any other pointer type (even those that are not Objective-C pointer types), this restriction is necessary. This scenario is common when casting between Foundation Framework objects and Core Foundation types (Core Foundation is an Apple software library that provides C-language APIs). A set of APIs is provided for ownership transfer of variables between ARC and non-ARC environments.
  • Autorelease pool blocks should be used to perform ARC-managed autorelease of objects.
  • You cannot call the Foundation Framework functions NSAllocateObject and NSDeallocateObject. These functions provide the capability to allocate and deallocate memory for an object in a specific memory zone. Because zone-based memory is no longer supported, these functions cannot be used.
  • You cannot use object pointers in C structures (structs). ARC does not perform memory management for dynamically allocated C structures, thus the compiler cannot determine where to insert the necessary retain and release messages.
  • You cannot use memory zones (NSZone). As mentioned, zone-based memory is no longer supported.
  • To properly cooperate with non-ARC code, you cannot create a method or a declared property (unless you explicitly choose a different getter) that begins with “copy”.
  • By default, ARC is not exception-safe: it does not end the lifetime of __strong variables whose scope is abnormally terminated by an exception, and it does not perform releases of objects that would occur at the end of a full-expression if that full-expression throws an exception. The compiler option -fobjc-arc-exceptions can be used to enable exception handling for ARC code. ARC does end the lifetimes of weak references when an exception terminates their scope, unless exceptions are disabled in the compiler.

ARC Lifetime Qualifiers

There are a set of ARC-specific qualifiers that declare the object lifetime for both regular variables and properties. The following are those for regular variables:

  • __strong: Any object created using alloc / init is retained for the lifetime of its current scope. This is the default setting for regular variables. The “current scope” usually means the braces in which the variable is declared (a method, for loop, if block, etc.)
  • __weak: The object can be destroyed at anytime. This is only useful if the object is somehow strongly referenced somewhere else. When destroyed, a variable with __weak is set to nil.
  • __unsafe_unretained: This is just like __weak but the pointer is not set to nil when the object is deallocated. Instead, the pointer is left dangling (i.e., it no longer points to anything useful).
  • __autoreleasing: Not to be confused with calling autorelease on an object before returning it from a method, this is used for passing objects by reference.

When using ARC lifetime qualifiers to declare variables for Objective-C objects, the correct syntax is

ClassName *qualifier varName;

ClassName is name/type of the class (OrderEntry, etc.), qualifier is the ARC lifetime qualifier (as previously listed), and varName is the name of the variable. If no qualifier is specified, the default is __strong. The ARC-specific lifetime qualifiers for properties are as follows:

  • strong: The strong attribute is equivalent to the retain attribute (the complete list of property attributes is provided in Appendix A).
  • weak: The weak attribute is similar to the assign attribute, except that if the affected property instance is deallocated, its instance variable is set to nil.

Under ARC, strong is the default ownership attribute for object-type properties.

Using ARC

Now you are going to develop an example program that uses ARC. You’ll take the program you built earlier that uses MRR memory management (OrderEntry) and create a new version that uses ARC memory management. This will enable you to get some hands-on experience programming with ARC and also see how it simplifies application memory management. As you may recall, the OrderEntry program you developed earlier consists of three classes: an OrderEntry class used to record product orders, and the dependent classes OrderItem and Address.

In Xcode, create a new project by selecting New image Project ... from the Xcode File menu. In the New Project Assistant pane, create a command-line application (choose Command Line Tool from the Mac OS X Application selection) and click Next. In the Project Options window, specify ARC Orders for the Product Name and select ARC memory management by checking the Use Automatic Reference Counting check box, and then click Next.

Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Now let’s add the OrderEntry, OrderItem, and Address classes. As you’ve done before, add each class to the project by selecting New image File ... from the Xcode File menu, selecting the Objective-C class template, naming the class appropriately (each is a subclass of NSObject), and then selecting the ARC Orders folder for the files location and the ARC Orders project as the target.

Your project now has files for three classes along with the main.m file. Next, implement the Address class. Proceeding as you did with the MRR Orders project, you’ll edit the Address class implementation by overriding the default init and dealloc methods provided by its superclass (NSObject). Select the Address.m file in the navigator pane, and then add the code, as shown in Listing 4-11.

Listing 4-11.  ARC Address Class Implementation

#import "Address.h"

@implementation Address
- (id) init
{
  if ((self = [super init]))
  {
    NSLog(@"Initializing Address object");
  }
  return self;
}

- (void)dealloc
{
  NSLog(@"Deallocating Address object");
}
@end

Listing 4-11 shows the init method is the same as that provided for MRR Address class. The dealloc method is different; it does not invoke dealloc on its superclass because this is automatically performed by ARC. Now let’s implement the ARC OrderItem class.

The ARC OrderItem interface is the same as that provided for the MRR OrderItem class. Select the OrderItem.h file and copy the interface shown in Listing 4-6. Next, select the ARC OrderItem.m file and implement the OrderItem class, as shown in Listing 4-12.

Listing 4-12.  ARC OrderItem Class Implementation

#import "OrderItem.h"

@implementation OrderItem
- (id) initWithName:(NSString *)itemName
{
  if ((self = [super init]))
  {
    NSLog(@"Initializing OrderItem object");
    name = itemName;
}
  return self;
}

- (void)dealloc
{
  NSLog(@"Deallocating OrderItem object);
}
@end

The custom initializer is very similar to that provided for the MRR OrderItem class. It differs in that it doesn’t invoke the retain method on the name instance variable, as you cannot send retain, release, or autorelease messages with ARC. The dealloc method is also different from that provided for the MRR OrderItem class; it merely logs a message to the output console. ARC automatically sends a release message to all the objects that the OrderItem class has an ownership interest in, and also invokes dealloc on its superclass.

Now you’ll implement the ARC OrderEntry class. The interface is identical to that of the MRR OrderEntry class. Select the ARC OrderEntry interface (OrderEntry.h) and copy the interface shown in Listing 4-8.

Next, select the OrderEntry.m file and implement the OrderEntry class, as shown in Listing 4-13.

Listing 4-13.  ARC OrderEntry Class Implementation

#import "OrderEntry.h"

@implementation OrderEntry
- (id) initWithId:(NSString *)oid
{
  if ((self = [super init]))
  {
    NSLog(@"Initializing OrderEntry object");
    orderId = oid;
    item = [[OrderItem alloc] initWithName:@"Doodle"];
    shippingAddress = [[Address alloc] init];
  }
  
  return self;
}

- (void)dealloc
{
  NSLog(@"Deallocating OrderEntry object with ID %@", orderId);
}
@end

The custom initializer is very similar to that provided for the MRR OrderEntry class. It differs in that it doesn’t invoke the retain method on the orderId instance variable; as noted previously, you cannot send retain/release/autorelease messages with ARC. The dealloc method is also different from that provided for the MRR OrderEntry class; it merely logs a message to the output console. ARC automatically sends a release message to all the objects that the OrderItem class has an ownership interest in, and also invokes dealloc on its superclass.

Now let’s implement the ARC Orders main() function. Select the main.m file in the navigator pane, and then update the main() function, as shown in Listing 4-14.

Listing 4-14.  ARC main() Function Implementation

#import <Foundation/Foundation.h>
#import "OrderEntry.h"

int main(int argc, const char * argv[])
{

  @autoreleasepool
  {
    // Create an OrderEntry object for manual release
    NSString *a1 = @"A1";
    NSString *orderId = [[NSString alloc] initWithString:a1];
    OrderEntry *entry = [[OrderEntry alloc] initWithId:orderId];
    
    // Set ID to nil to verify that value was retained by ARC!
    a1 = nil;
    
    // View results for new order entry, should display valid ID!
    NSLog(@"New order, ID = %@, item: %@", entry->orderId, entry->item->name);
    
    // Now set OrderEntry object to nil, it can now be deallocated by ARC!
    entry = nil;
    
    // Create another OrderEntry object
    OrderEntry *autoEntry = [[OrderEntry alloc] initWithId:@"A2"];
    NSLog(@"New order, ID = %@, item: %@", autoEntry->orderId,          autoEntry->item->name);
  }
  return 0;
}

The main() function demonstrates ARC memory management by creating OrderEntry objects. They are automatically released by ARC when the objects are no longer in use. The function also demonstrates that classes developed with ARC acquire ownership interest in dependent objects as necessary, thereby preventing objects from being changed or deallocated while they are still in use.

Now save, compile, and run the ARC Orders program, and observe the messages in the output pane (as shown in Figure 4-7).

9781430250500_Fig04-07.jpg

Figure 4-7. Testing the ARC OrderEntry project

The messages show the sequence of calls to initialize and deallocate the dynamically created objects. You can see that ARC memory management performed correctly, releasing all dynamically created objects when they were no longer in use. You can also perform an initial first-check of program memory management by selecting Analyze from the Xcode Product menu. Comparing the MRR Orders project and the ARC Orders project, notice how it significantly reduced the amount of code you had to write. As your projects grow in size and complexity, the advantages of ARC become even more pronounced. Because ARC can be selected on a file-by-file basis and ARC-compiled code can be used alongside existing code built using MRR, there’s no reason not to use it on your projects. In fact, ARC is the Apple-recommended mechanism for memory management on new Objective-C projects.

Avoiding Circular References

The Objective-C reference counting model is implemented by taking ownership interest in an object (via a retain message), and then releasing ownership interest (via a release message) when that object is no longer needed. This process is automated with ARC, which automatically inserts retain/release/autorelease messages in the code where needed. However, in an object graph, a problem of circular references can arise if two objects in the graph have circular references. For example, in the preceding ARC Orders project, an OrderEntry object has an OrderItem instance variable, which is by default a strong reference. Now if an OrderItem object also had an OrderItem instance variable, this could create a circular reference between the two objects. If this were to occur, neither object could ever be deallocated, therefore causing a memory leak. In effect, the OrderEntry object could not be released until the OrderItem object is released, and the OrderItem object couldn’t be released until the OrderEntry object is released. The solution to this problem is to use weak references. A weak reference is a non-owning relationship; that is, the object declared as a weak reference is not owned by the declaring object, hence eliminating the circular reference. The Apple Objective-C convention for object graphs is for the parent object to maintain a strong reference to each of its child objects, while the child objects maintain a weak reference (if necessary) to the parent object. Thus, in this example, the OrderEntry object would have a strong reference to its OrderItem object, while the OrderItem object would have a weak reference to its parent, OrderEntry object. A weak reference would be declared in the OrderItem class (as shown in Listing 4-15).

Listing 4-15.  Declaring a Weak Reference

#import <Foundation/Foundation.h>

@interface OrderItem : NSObject

{
@public NSString *name;
OrderEntry *__weak entry;
}

- (id) initWithName:(NSString *)itemName;

@end

When destroyed, the entry variable is set to nil, thereby avoiding a circular reference.

Roundup

This chapter examined the details of Objective-C memory management, including the Objective-C memory model, how memory is allocated and released for Objective-C programs, and how to use the two methods provided for Objective-C memory management. The following are the key takeaways:

  • At runtime, an Objective-C program creates objects (via the NSObject alloc method) stored in dynamically allocated memory referred to as the program heap. Dynamic object creation implies the need for memory management, because objects created on the heap never go out of scope. Typical results of incorrect or no memory management include memory leaks and dangling pointers.
  • Objective-C memory management is implemented using reference counting, a technique whereby unique references to an object are used to determine whether or not an object is still in use. If an object’s reference count drops to zero, it is considered no longer in use and its memory is deallocated by the runtime system.
  • The Apple Objective-C development environment provides two mechanisms that can be used for memory management: Manual Retain-Release (MRR) and Automatic Reference Counting (ARC).
  • With MRR, you write your code to explicitly manage the life cycle of objects, obtaining an ownership interest in objects that you create/need to use, and releasing that ownership interest when you no longer need the object(s).
  • ARC employs the same reference counting model as MRR, but utilizes the compiler to manage the object life cycle. At program compilation, the compiler analyzes the source code, determines the life cycle requirements for dynamically created objects, and then automatically inserts retain and release messages where necessary in the compiled code.
  • ARC is augmented with new object lifetime qualifiers that are used to explicitly declare the lifetime of object variables and properties, and also includes functionality (weak references) that can be used to prevent circular references in object graphs.
  • ARC can be used across a project or on a per-file basis, thereby enabling code that uses ARC to still work with existing code that is not ARC-compliant. Apple also provides a conversion tool for migrating existing Objective-C code to ARC. ARC is the recommended tool for memory management on all new Objective-C projects.

Memory management is very important for Objective-C software development. It has a major impact on the application user experience and the overall system operation. A well-written Objective-C program uses only as much memory as it requires, and doesn’t leak memory or try to access objects that are no longer in use. You now understand the Objective-C memory model and the methods provided for application memory management: MRR and ARC (the Apple-recommended approach). In the next chapter, you’re going to tackle another key element of the Objective-C platform—the preprocessor. And guess what? This means that you’ll be learning a new programming language as well! So why not take a break and give yourself a well-deserved rest. When you’re ready, turn the page to begin the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.178.53