17. Memory Management

We have focused on the topic of memory management throughout this book. You should understand by now when you are responsible for releasing objects and when you are not. Even though the examples in this book have all been very small, nevertheless we emphasized the importance of paying attention to memory management to teach good programming practice and to develop leak-free programs.

Depending on the type of application you're writing, judicious use of memory can be critical. For example, if you're writing an interactive drawing application that creates many objects during the execution of the program, if you're not careful, your program might continue to consume more and more memory resources as it runs. In such cases, it becomes your responsibility to intelligently manage those resources and free them when they're no longer needed. This means freeing resources during the program's execution rather than just waiting until the end.

In this chapter, you will learn about Foundation's memory allocation strategy in more detail. This involves a more thorough discussion of the autorelease pool and the idea of retaining objects. You will also learn about an object's reference count.

The Autorelease Pool

You are familiar with the autorelease pool from previous program examples in this second part of the book. When dealing with Foundation programs, you must set up this pool yourself to use the Foundation objects.1 This pool is where the system keeps track of your objects for later release. The pool can be set up by your application with a call, like so:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

Once the pool is set up, Foundation automatically adds certain arrays, strings, dictionaries, and other objects to this pool. When you're done using the pool, you can release the memory it uses with the release method:

[pool release];

The autorelease pool gets its name from the fact that any objects that have been marked as autorelease and therefore added to the pool are automatically released when the pool itself is released. In fact, you can have more than one autorelease pool in your program, and they can be nested as well.

Multiple Autorelease Pools

If your program generates a large number of temporary objects (which can easily happen when executing code inside a loop), you might need to create multiple autorelease pools in your program. For example, the code fragment

NSAutoreleasePool  *tempPool;
  ...
for (i = 0; i < n; ++i) {
  tempPool = [[NSAutoReleasePool alloc] init];
  // lots of work with temporary objects here
  [tempPool release];
}

illustrates how you could set up autorelease pools to release the temporary objects created by each iteration of the for loop.

You should note that the autorelease pool doesn't contain the actual objects themselves—only a reference to the objects that are to be released when the pool is released.

You can add an object to the current autorelease pool for later release by sending it an autorelease message:

[myFraction autorelease];

The system then adds myFraction to the autorelease pool for automatic release later. As you'll see, the autorelease method is useful for marking objects from inside a method for later disposal.

Reference Counting

When we talked about the basic Objective-C object class Object, we noted that memory is allocated with the alloc method and could subsequently be released with a free message. Unfortunately, it's not always that simple. An object you create can be referenced in several places by a running application; it also might be stored in an array or referenced by an instance variable someplace else, for example. You can't free up the memory used by an object until you are certain that everyone is done using that object.

Luckily, the Foundation framework provides an elegant solution for keeping track of the number of references to an object. It involves a fairly straightforward technique called reference counting. The concept is as follows: When an object is created, its reference count is set to 1. Each time you need to ensure that the object be kept around, you increment its reference count by 1 by sending it a retain message, like so:

[myFraction retain];

Some of the methods in the Foundation framework also increment this reference count, such as when an object is added to an array.

When you no longer need an object, you decrement its reference count by 1 by sending it a release message, like this:

[myFraction release];

When the reference count of an object reaches 0, the system knows that the object is no longer needed (because, in theory, it is no longer referenced), so it frees up (deallocates) its memory. This is done by sending the object a dealloc message.

Successful operation of this strategy requires diligence on the part of you, the programmer, to ensure that the reference count is appropriately incremented and decremented during program execution. Some of this, but not all, is handled by the system, as you'll see.

Let's take a look at reference counting in a little more detail. The retainCount message can be sent to an object to obtain its reference (or retain) count. You will normally never need to use this method, but it's useful here for illustrative purposes (see Program 17.1).

Program 17.1.


// Introduction to reference counting

#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSValue.h>

int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSNumber          *myInt = [NSNumber numberWithInt: 100];
  NSNumber          *myInt2;
  NSMutableArray    *myArr = [NSMutableArray array];

  printf ("myInt retain count = %x ", [myInt retainCount]);

  [myArr addObject: myInt];
  printf ("after adding to array = %x ", [myInt retainCount]);

  myInt2 = myInt;
  printf ("after asssignment to myInt2 = %x ",
                   [myInt retainCount]);

  [myInt retain];
  printf ("myInt after retain = %x ", [myInt retainCount]);
  printf ("myInt2 after retain = %x ", [myInt2 retainCount]);

  [myInt release];
  [printf ("after release = %x ", [myInt retainCount]);

  [myArr removeObjectAtIndex: 0];
  printf ("after removal from array = %x ", [myInt retainCount]);

  [pool release];
  return 0;

}


Program 17.1. Output


myInt retain count = 1
after adding to array = 2
after asssignment to myInt2 = 2
myInt after retain = 3
myInt2 after retain = 3
after release = 2
after removal from array = 1


The NSNumber object myInt is set to the integer value 100, and the output shows it has an initial retain count of 1. Next, the object is added to the array myArr using the addObject: method. Note that its reference count then goes to 2. This is done automatically by the addObject: method; if you check your documentation for the addObject: method, you will see this fact described there. Adding an object to any type of collection increments its reference count. That means if you subsequently release the object you've added, it will still have a valid reference from within the array and won't be deallocated.

Next, you assign myInt to myInt2. Note that this doesn't increment the reference count—this could be potential trouble later. For example, if the reference count for myInt were decremented to 0 and its space released, myInt2 would have an invalid object reference (remember that the assignment of myInt to myInt2 doesn't copy the actual object, only the pointer in memory to where the object is located).

Because myInt now has another reference (through myInt2), you increment its reference count by sending it a retain message. This is done in the next line of Program 17.1. As you can see, after sending it the retain message, its reference count becomes 3. The first reference is the actual object itself, the second from the array, and the third from the assignment. Although storing the element in the array creates an automatic increase in the reference count, assigning it to another variable does not, so you must do that yourself. Notice from the output that both myInt and myInt2 have a reference count of 3; that's because they both reference the same object in memory.

Let's assume you're done using the myInt object in your program. You can tell the system that by sending a release message to the object. As you can see, its reference count then goes from 3 back down to 2. Because it's not 0, the other references to the object (from the array and through myInt2) remain valid. The memory used by the object is not deallocated by the system as long as it has a nonzero reference count.

If you remove the first element from the array myArr using the removeObjectAtIndex: method, you'll note that the reference count for myInt is automatically decremented to 1. In general, removing an object from any collection has the side effect of decrementing its reference count. This implies that the following code sequence

myInt = [myArr ObjectAtIndex: 0];
  ...
[myArr removeObjectAtIndex: 0]
  ...

could lead to trouble. That's because, in this case, the object referenced by myInt can become invalid after the removeObjectAtIndex: method is invoked if its reference count is decremented to 0. The solution here, of course, is to retain myInt after it is retrieved from the array so that it won't matter what happens to its reference from other places.

Reference Counting and Strings

Program 17.2 shows how reference counting works for string objects.

Program 17.2.


// Reference counting with string objects

#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>


int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSString          *myStr1 = @"Constant string";
  NSString          *myStr2 = [NSString stringWithString:
                                  @"string 2"];
  NSMutableString   *myStr3 = [NSMutableString stringWithString:
                                  @"string 3"];
  NSMutableArray  *myArr = [NSMutableArray array];

  printf ("Retain count: myStr1: %x, myStr2: %x, myStr3: %x ",
          [myStr1 retainCount], [myStr2 retainCount],
          [myStr3 retainCount]);

  [myArr addObject: myStr1];
  [myArr addObject: myStr2];
  [myArr addObject: myStr3];

  printf ("Retain count: myStr1: %x, myStr2: %x, myStr3: %x ",
          [myStr1 retainCount], [myStr2 retainCount],
          [myStr3 retainCount]);

  [myStr1 retain];
  [myStr2 retain];
  [myStr3 retain];

  printf ("Retain count: myStr1: %x, myStr2: %x, myStr3: %x ",
          [myStr1 retainCount], [myStr2 retainCount],
          [myStr3 retainCount]);

  // Bring the reference count of myStr2 and myStr3 back down to 2

  [myStr2 release];
  [myStr3 release];

  [pool release];
  return 0;
}


Program 17.2. Output


Retain count: myStr1: ffffffff, myStr2: 1, myStr3: 1
Retain count: myStr1: ffffffff, myStr2: 2, myStr3: 2
Retain count: myStr1: ffffffff, myStr2: 3, myStr3: 3


The NSString object myStr1 is assigned the NSConstantString @"Constant string". Space for constant strings is allocated differently in memory than other objects. They have no reference counting mechanism because they can never be released, which is why when the retainCount message is sent to myStr1, it returns a value of 0xffffffff. (This value is actually defined as the largest possible unsigned integer value, or UINT_MAX, in the standard header file <limits.h>.)

The variables myStr2 and myStr3 are set to strings made by copies of constant character strings. These strings do have reference counts, as verified by the output. These reference counts can be changed by adding these strings to an array or by sending them retain messages, as verified by the output from the last two printf calls. Both of these objects were added to the autorelease pool by Foundation's stringWithString: method when they were created. The array myArr was also added to the pool by Foundation's array method.

Before the autorelease pool itself is released, the objects myStr2 and myStr3 are each released. This brings their reference counts both down to 2. The release of the autorelease pool then decrements the reference counts of these two objects to 0, which causes them to be deallocated. How does that happen? When the autorelease pool is released, each of the objects in the pool gets a release message sent to it for each time it was sent an autorelease message. Because the strings objects myStr2 and myStr3 were added to the autorelease pool when they were created by the stringWithString: method, they each are sent a release message. That brings their reference counts down to 1. When an array in the autorelease pool is released, each of its elements also is released. Therefore, when myArr is released from the pool, each of its elements—which includes myStr2 and myStr3—is sent release messages. This brings their reference counts down to 0, which then causes them to be deallocated.

You have to be careful not to overrelease an object. In Program 17.2, if you were to bring the reference count of either myStr2 or mystr3 below 2 before the pool was released, the pool would contain a reference to an invalid object. Then, when the pool was released, the reference to the invalid object would most likely cause the program to terminate abnormally with a message similar to this:

./c: line 1: 1384 Segmentation fault   a.out
c retain2B: exited with status 139

Reference Counting and Instance Variables

You also have to pay attention to reference counts when you deal with instance variables. For example, recall the setName: method from your AddressCard class:

-(void) setName: (NSString *) theName
{
    [name release];
    name = [[NSString alloc] initWithString: theName];
}

Suppose we had defined setName: this way instead and did not have it take ownership of its name object:

-(void) setName: (NSString *) theName
{
  name = theName;
}

This version of the method takes a string representing the person's name and stores it in the name instance variable. It seems straightforward enough, but consider the following method call:

NSString  *newName;
  ...
[myCard setName: newName];

Suppose newName is a temporary storage space for the name of the person you want to add to the address card and that later you want to release it. What do you think would happen to the name instance variable in myCard? That's correct; its name field would no longer be valid because it would reference an object that had been destroyed. That's why your classes need to own their own member objects: You don't have to worry about those objects inadvertently being deallocated or modified.

The next few examples illustrate this point in more detail. Let's start by defining a new class called ClassA that has one instance variable: a string object called str. You'll just write setter and getter methods for this variable (see Program 17.3).

Program 17.3.


// Introduction to reference counting

#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>

@interface ClassA: NSObject
{
  NSString *str;
}

-(void) setStr: (NSString *) s;
-(NSString *) str;
@end

@implementation ClassA;
-(void) setStr: (NSString *) s
{
  str = s;
}


-(NSString *) str
{
  return str;
}
@end

int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSString  *myStr = [NSString stringWithString: @"A string"];
  ClassA   *myA = [[ClassA alloc] init];

  printf ("myStr retain count: %x ", [myStr retainCount]);

  [myA setStr: myStr];
  printf ("myStr retain count: %x ", [myStr retainCount]);

  [myA release];
  [pool release];
  return 0;
}


Program 17.3. Output


myStr retain count: 1
myStr retain count: 1


The program simply allocates a ClassA object called myA and then invokes the setter method to set it to the NSString object specified by myStr. The reference count for myStr is 1 both before and after the setStr method is invoked, as you would expect because the method simply stores the value of its argument into its instance variable str. Once again, however, if the program were to release myStr after calling the setStr method, the value stored inside the str instance variable would become invalid because its reference count would be decremented to 0 and the memory space occupied by the object it references would be deallocated.

This does in fact happen in Progam 17.3 when the autorelease pool is released. Even though we didn't add it to that pool explicitly ourselves, when we created the string object myStr using the stringWithString: method, it was added to the autorelease pool by that method. When the pool was released, so was myStr. Any attempt to access it after the pool was released would therefore be invalid.

Program 17.4 makes a change to the setStr: method to retain the value of str. This protects you from someone else later releasing the object str references.

Program 17.4.


// Retaining objects

#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>

@interface ClassA: NSObject
{
  NSString *str;
}

-(void) setStr: (NSString *) s;
-(NSString *) str;
@end

@implementation ClassA;
-(void) setStr: (NSString *) s
{
  str = s;
  [str retain];
}

-(NSString *) str
{
  return str;
}
@end

int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSString  *myStr = [NSString stringWithString: @"A string"];
  ClassA   *myA = [[ClassA alloc] init];

  printf ("myStr retain count: %x ", [myStr retainCount]);

  [myA setStr: myStr];
  printf ("myStr retain count: %x ", [myStr retainCount]);

  [myStr release];
  printf ("myStr retain count: %x ", [myStr retainCount]);

  [myA release];
  [pool release];
  return 0;
}


Program 17.4. Output


myStr retain count: 1
myStr retain count: 2
myStr retain count: 1


You see that the reference count for myStr is bumped to 2 after the setStr: method is invoked. So, this particular problem has been solved. Subsequently releasing myStr in the program makes its reference through the instance variable still valid because its reference count is still 1.

Because you allocated myA in the program using alloc, you are still responsible for releasing it yourself. Instead of having to worry about releasing it yourself, you could have added it to the autorelease pool by sending it an autorelease message:

[myA autorelease];

This can be done immediately after the object is allocated if you want. Remember, adding an object to the autorelease pool doesn't release it or invalidate it; it just marks it for later release. You can continue to use the object until it is deallocated, which happens when the pool is released if the reference count of the object becomes 0 at that time.

You are still left with some potential problems that you might have spotted. Your setStr: method does its job of retaining the string object it gets as its argument, but when does that string object get released? Also, what about the old value of the instance variable str that you are overwriting? Shouldn't you release its value to free up its memory? Program 17.5 provides a solution to this problem.

Program 17.5.


// Introduction to reference counting

#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>

@interface ClassA: NSObject
{
  NSString *str;
}

-(void) setStr: (NSString *) s;
-(NSString *) str;
-(void) dealloc;
@end

@implementation ClassA;
-(void) setStr: (NSString *) s
{
  // free up old object since we're done with it
  [str autorelease];

  // retain argument in case someone else releases it
  str = [s retain];
}

-(NSString *) str
{
  return str;
}

-(void) dealloc {
  printf ("ClassA dealloc ");
  [str release];
  [super dealloc];
}
@end

int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSString  *myStr = [NSString stringWithString: @"A string"];
  ClassA  *myA = [[ClassA alloc] init];

  printf ("myStr retain count: %x ", [myStr retainCount]);
  [myA autorelease];

  [myA setStr: myStr];
  printf ("myStr retain count: %x ", [myStr retainCount]);

  [pool release];
  return 0;
}


Program 17.5. Output


myStr retain count: 1
myStr retain count: 2
ClassA dealloc


The setStr: method first takes whatever is currently stored in the str instance variable and autoreleases it.2 That is, it makes it available for later release. This is important if the method might be called many times throughout the execution of a program to set the same field to different values. Each time a new value is stored, the old value should be marked for release. After the old value is released, the new one is retained and stored in the str field. The message expression

str = [s retain];

takes advantage of the fact that the retain method returns its receiver.

The dealloc method is not new. You encountered it before in Chapter 15, “Numbers, Strings, and Collections,” with your AddressBook and AddressCard classes. Overriding dealloc provides a tidy way for you to dispose of the last object referenced by your str instance variable when its memory is to be released (that is, when its reference count becomes 0). In such a case, the system calls the dealloc method, which is inherited from NSObject and which you normally won't want to override. In the case of objects you retain, allocate with alloc or copy (with one of the copy methods discussed in the next chapter) inside your methods, you might need to override dealloc so that you get a chance to free them up. The statements

[str release];
[super dealloc];

first release the str instance variable and then call the parent's dealloc method to finish the job.

The printf was placed inside the dealloc method to print a message when it is called. We did this just to verify that the ClassA object is deallocated properly when the autorelease pool is released.

You might have spotted one last pitfall with the setter method setStr. Take another look at Program 17.5. Suppose myStr were a mutable string instead of an immutable one, and further suppose that one or more characters in myStr were changed after invoking setStr. Changes to the string referenced by myStr would also affect the string referenced by the instance variable because they reference the same object. Reread that last sentence to make sure you understand that point. Also, realize that setting myStr to a completely new string object does not cause this problem. The problem occurs only if one or more characters of the string are modified in some way.

The solution to this particular problem is to make a new copy of the string inside the setter if you want to protect it and make it completely independent of the setter's argument. This is why you chose to make a copy of the name and email members in the setName: and setEmail: AddressCard methods in Chapter 15.

Back to the Autorelease Pool

Let's take a look at one last program example in this chapter to ensure that you really understand how reference counting, retaining, and releasing/autoreleasing objects work. Examine Program 17.6, which defines a dummy class called Foo with one instance variable and only inherited methods.

Program 17.6.


#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>

@interface Foo: NSObject
{
  int x;
}
@end

@implementation Foo;
@end

int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  Foo   *myFoo = [[Foo alloc] init];

  printf ("myFoo retain count = %x ", [myFoo retainCount]);

  [pool release];
  printf ("after pool release = %x ", [myFoo retainCount]);

  pool = [[NSAutoreleasePool alloc] init];
  [myFoo autorelease];
  printf ("after autorelease = %x ", [myFoo retainCount]);

  [myFoo retain];
  printf ("after retain = %x ", [myFoo retainCount]);

  [pool release];
  printf ("after second pool release = %x ", [myFoo retainCount]);

  [myFoo release];
  return 0;
}


Program 17.6. Output


myFoo retain count = 1
after poolrelease = 1
after autorelease = 1
after retain = 2
after second pool release = 1


The program allocates a new Foo object and assigns it to the variable myFoo. Its initial retain count is 1, as you've already seen. This object is not a part of the autorelease pool yet, so releasing the pool does not invalidate the object. A new pool is then allocated and myFoo is added to the pool by sending it an autorelease message. Notice again that its reference count doesn't change because adding an object to the autorelease pool does not affect its reference count—it only marks it for later release.

Next, you send myFoo a retain message. This changes its reference count to 2. When you subsequently release the pool the second time, the reference count for myFoo is decremented by 1 because it was previously sent an autorelease message and therefore is sent a release message when the pool is released.

Because myFoo was retained before the pool was released, its reference count after decrementing is still greater than 0. Therefore, myFoo survives the pool release and is still a valid object. Of course, you must now release it yourself, which is what we do in Program 17.6 to properly clean up and avoid memory leaks.

Reread this explanation of the autorelease pool again if it still seems a little fuzzy to you. When you understand Program 17.6, you will have a thorough understanding of the autorelease pool and how it works.

Summary of Memory Management Rules

Let's summarize what you've learned about memory management in this chapter:

• Releasing an object can free up its memory, which can be a concern if you're creating many objects during the execution of a program. A good rule is to release objects you've created or retained when you're done with them.

• Sending a release message does not necessarily destroy an object. When an object's reference count is decremented to 0, the object is destroyed. The system does this by sending the dealloc message to the object to free its memory.

• The autorelease pool provides for the automatic release of objects when the pool itself is released. The system does this is by sending a release message to each object in the pool for each time it was autoreleased. Each object in the autorelease pool whose reference count goes down to 0 is sent a dealloc message to destroy the object.

• If you no longer need an object from within a method but need to return it, send it an autorelease message to mark it for later release. The autorelease message does not affect the reference count of the object. So, it enables the object to be used by the message sender but still be freed up later when the autorelease pool is released.

• When your application terminates, all the memory taken by your objects is released, whether they were in the autorelease pool.

When you develop more sophisticated applications (such as Cocoa applications), autorelease pools can be created and destroyed during execution of the program (for Cocoa applications, that happens each time an event occurs). In such cases, if you want to ensure that your object survives automatic deallocation when the autorelease pool itself is released, you need to explicitly retain it. All objects that a have a reference count greater than the number of autorelease messages they have been sent will survive the release of the pool.

• If you directly create an object using an alloc or copy method (or with an allocWithZone:, copyWithZone:, or mutableCopy method), you are responsible for releasing it. For each time you retain an object, you should release or autorelease that object.

• You don't have to worry about releasing objects that are returned by methods other than those noted in the previous rule. It's not your responsibility; those objects should have been autoreleased by those methods. That's why you needed to create the autorelease pool in your program in the first place. Methods such as stringWithString: automatically add newly created string objects to the pool by sending them autorelease messages. If you don't have a pool set up, you get a message that you tried to autorelease an object without having a pool in place.

The memory management techniques described in this chapter will suffice for most applications. However, in more advanced cases, such as when writing multithreaded applications, you might need to do more. See Appendix E, “Resources,” for more details.

Exercises

  1. Write a program to test the effects of adding and removing entries in a dictionary on the reference count of the objects you add and remove.
  2. What effect do you think the NSArray's replaceObjectAtIndex:withObject: method will have on the reference count of the object that is replaced in the array? What effect will it have on the object placed into the array? Write a program to test it. Then consult your documentation on this method to verify your results.
  3. Return to the Fraction class you worked with throughout Part I, “The Objective-C Language.” For your convenience, it is listed in Appendix D, “Fraction and Address Book Examples.” Modify that class to work under the Foundation framework. Then add messages as appropriate to the various MathOps category methods to add the fractions resulting from each operation to the autorelease pool. When that is done, can you write a statement like this:

    [[fractionA add: fractionB] print];

    without leaking memory? Explain your answer.

  4. Return to your AddressBook and AddressCard examples from Chapter 15. Modify each dealloc method to print a message when the method is invoked. Then run some of the sample programs that use these classes to ensure that a dealloc message is sent to every AddressBook and AddressCard object you use in the program before reaching the end of main.
..................Content has been hidden....................

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