[iOS开发站在巨人肩膀上]之How to avoid memory leaks in iPhone applications


本文来源:http://www.codeproject.com/KB/iPhone/avoidiphoneleaks.aspx

Introduction

This article lists some tips to avoid memory leaks in your iPhone apps.

Ownership

Ownership is the overall idea behind how memory management should work on the iPhone. When an object has an owner, they are responsible for releasing the object when they have finished using it. An object can have more than one owner, and when it has no owners, it is set for de-allocation.

Ownership is made when creating an object with allocnew, or copy, when calling retain on an object, or when calling Cocoa functions that have Create or Copy in their name. There are two ways memory is released: either explicitly calling release on an object, or using the auto-release pool.

Behind ownership is a system called reference counting. Most objects in the iPhone SDK are strongly referenced, this means they use reference counting.

When you create an object, it will have a reference count of 1, and calling retain on an object will increment the reference count by 1. Calling release will decrement the reference count by 1; when the reference count reaches zero, the object is de-allocated. Calling autorelease instead of release means the object will be de-allocated at a later time automatically.

Objects can also be weakly referenced, meaning that a reference count isn't kept and the object will need to be de-allocated manually.

When should you use retain? When you want to prevent an object from being de-allocated before you use it.

Every time you use copyallocretain, or a Cocoa function that has Create or Copy in its name, you need to have a matching release or autorelease.

The developer should think about objects in terms of ownership, and not worry about reference counts. If you have matching retain and release calls, it is obvious that you will have matching +1 and -1 additions to the reference count.

Note: It may be tempting to use [object retainCount], but the values this returns can be misleading due to the behind the scenes code in the SDK. It is not recommended to manage memory this way.

Auto-release

Objects set to auto-release mean that they do not need to be explicitly released because they will be released when an auto-release pool is popped. The iPhone has an auto-release pool that runs on the main thread which usually releases objects at the end of an event loop. When you create your own threads, you must create your own auto-release pool.

On the iPhone, there are convenience constructors; objects created with convenience constructors are set to auto-release.

Examples:

NSString* str0 = @"hello";
NSString* str1 = [NSString stringWithString:@"world"];
NSString* str2 = str1;

An allocated object can be set to auto-release like this:

NSString* str = [[NSString alloc] initWithString:@"the flash?"];
[str autorelease];

Or like this:

NSString* str = [[[NSString alloc] initWithString:@"batman!"] autorelease];

Ownership for auto-released objects is relinquished when the pointer goes out of scope or when an auto-release pool is popped.

The built in auto-release pool is usually popped at the end of an event loop, but this may not be desired when you have a loop that is allocating a lot of memory in each iteration. In that case, you can create an auto-release pool in the loop. Auto-release pools can be nested so the objects allocated in the inner pool will be released when that pool is popped. In the example below, objects will be released at the end of each iteration.

for (int i = 0; i < 10; ++i)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString* str = [NSString stringWithString:@"hello world"];
    [self ProcessMessage: str];
    [pool drain];
}

Note: At the time of writing, the iPhone doesn't support garbage collecting, so drain will work the same asreleasedrain is often used in cases where you want to port the program to OSX, or if garbage collecting is added to the iPhone later. drain provides a hit to the garbage collector that memory is being released.

Returning a pointer to an object

When following the rules of ownership, the developer needs to be aware of what functions have ownership of an object. This is an example of returning a pointer to an object and releasing it.

Wrong way
- (NSMutableString*) GetOutput
{
    NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
    return output;
}
- (void) Test
{
    NSMutableString* obj = [self GetOutput];
    NSLog(@"count: %d", [obj retainCount]);
    [obj release];
}

In this example, output is owned by GetOutput. Having Test release obj violates the rules in the Coccoa Memory Management Guide; this will not leak memory, but it is not a good practice because Test shouldn't release an object it doesn't own.

If GetOutput is called, the caller shouldn't need to know if the object returned from GetOutput is retained or not. It is therefore free to retain and release the returned object without disrupting any other code in the application.

Correct way
- (NSMutableString*) GetOutput
{
    NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
    return [output autorelease];
}
- (void) Test
{
    NSMutableString* obj = [self GetOutput];
    NSLog(@"count: %d", [obj retainCount]);
}

In the second example, output is set to auto-release when GetOutput returns. The reference count for output is decremented, and GetObject is relinquishing its ownership of output. The Test function is now free to retain and release the object, and be sure it won't leak when it's done.

In the example, obj is set to auto-release, so the Test function will not have ownership of it when the function ends, but what if it wanted to store the object somewhere else?

The object will then need to be retained by a new owner.

Setters

A setter function must retain the object it is storing, which means claiming ownership. If we want to create a setter function, we need to do two things before assigning a new pointer to our member variable.

In the function:

- (void) setName:(NSString*)newName

First, we would decrement the reference count of our member variable:

[name release];

This will allow the name object to be de-allocated if the reference count is zero, but it will allow any other owners of the object to continue to use the object.

Then, we will increment the reference count of the new NSString object:

[newName retain];

So, newName will not be de-allocated when the setName selector finishes. The object newName now points to is different to the one name points to, with a different reference count.

Now we can set name to point to the newName object:

name = newName;

But what if name and newName are the same object? We can't release it with the possibility of it being de-allocated and then retain it!

Simply retain the incoming object before releasing the stored object:

[newName retain];
[name release];
name = newName;

Now if the objects are the same, it will up the reference count then subtract from it, causing it to stay the same before assigning it.

Another way to do this is to use the Objective-C properties.

A property for an object is declared like so:

@property(nonatomic, retain) NSString *name;
  1. nonatomic means there is no blocking for multiple threads accessing the data at the same time. atomic will lock the data for a single thread, but is slower, so it is not used unless necessary.
  2. retain means that we want to retain the newName object.

Instead of retain, we could use copy:

@property(nonatomic, copy) NSString *name;

This would be the same as a function like this:

- (void) setName:(NSString*)newName
{
    NSString* copiedName = [newName copy];
    [name release];
    name = copiedName;
    [name retain];
    [copiedName release];
}

Here, newName is copied to copiedName, now copiedName has a copy of the string and owns it. name is released and copiedName is assigned to namename then retains the string, so both copiedName and name own it. Finally,copiedName releases the object and name is the only owner of the copied string.

Setters like this are imported to retain the member object, if we had a function like this:

- (void) Test
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   // do something...
   name = [self GetOutput];
   // do something else...
   NSLog(@"Client Name before drain: %@", name);    
   [pool drain];     
   NSLog(@"Client Name after drain: %@", name);
}

name would be undefined after the call to drain because name will be released when the pool is drained.

If we replaced the assignment with:

[self setName:[self GetOutput]];

Then name will be owned by the class and be retained for use until release is called.

But when do we release the object?

Since name is a member variable, the safest place to release it is in the dealloc function of the class it belongs to.

- (void)dealloc
{
   [name release];
   [super dealloc];
}

Note: As dealloc is not always called, relying on objects to be released in dealloc to trigger something could be dangerous. On exit, the iPhone OS may clear all the application memory before dealloc is called.

When assigning an object with a setter, be careful of lines like this:

[self setName:[[NSString alloc] init]];

name will be set fine, but alloc does not have a matching release. A better way would be something like this:

NSString* s = [[NSString alloc] init];
[self setName:s];
[s release];

Or with autorelease:

[self setName:[[[NSString alloc] init] autorelease]];

Auto-Release pools

Auto-release pools will release objects that are assigned in between their allocation and drain functions.

In the function below, we have a function that has a loop. In this loop, we are assigning a copy of NSNumber tomagicNumber. We are also setting magicNumber to be auto-released. In this example, we want to drain the pool on each iteration (this can save memory for loops with a large amount of assignments).

- (void) Test
{    
   NSString* clientName = nil;
   NSNumber* magicNumber = nil;
   for (int i = 0; i < 10; ++i)
{
       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    
       magicNumber = [[self GetMagicNumber] copy];
       [magicNumber autorelease];
       if (i == [magicNumber intValue])
       {
            clientName = [self GetOutput];
       }
       [pool drain];
   }
   if (clientName != nil)
   {
           NSLog(@"Client Name: %@", clientName);
   }
}

The problem is that clientName is assigned and released in the local auto-release pool. So when the loop finishes,clientName has already been released and any further use of clientName will be undefined.

In this case, we can retain clientName after we assign it, and release it when we are done:

- (void) Test
{    
   NSString* clientName = nil;
   NSNumber* magicNumber = nil;
   for (int i = 0; i < 10; ++i)
   {
       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    
       magicNumber = [[self GetMagicNumber] copy];
       [magicNumber autorelease];
       if (i == [magicNumber intValue])
       {
           clientName = [self GetOutput];
           [clientName retain]; 
       } 
       [pool drain];
   }
   if (clientName != nil)
   {
       NSLog(@"Client Name: %@", clientName);
       [clientName release];
   }
}

We have taken ownership of clientName from the period between the retain and release calls. By adding a pair of retain and release calls, we are saying that clientName will not be released until release is explicitly called.

Collections

When an object is added to a collection, it will be owned by the collection.

In this example, we allocate a string; it now has one owner:

NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];

We then add it to the array; now it has two owners:

[array addObject: str];

We can safely release the string, leaving it to be owned only by the array:

[str release];

When a collection is released, it will release all its objects as well.

NSMutableArray* array = [[NSMutableArray alloc] init];
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];
[array addObject: str];        
[array release];

In the above example, we allocate an array, allocate a string, add the string to the array, and release the array. This leaves the string with one owner, and it will not be de-allocated until we call [str release].

Passing in pointers with threads

In this function, we are passing in the string input to the function DoSomething, then releasing input.

- (void) Test
{
   NSMutableString* input = [[NSMutableString alloc] initWithString:@"batman!"];
   [NSThread detachNewThreadSelector:@selector(DoSomething:) toTarget:self withObject:input];
   [input release];
}

detatchNewThreadSelector ups the reference count for the input object and releases it when the thread finishes. This is why we can release input right after starting the thread no matter when the functionDoSomething starts or finishes.

- (void) DoSomething:(NSString*)str
{
   [self performSelectorOnMainThread:@selector(FinishSomething:)
         withObject:str waitUntilDone:false];
}

performSeclectorOnMainThread will also retain the object passed in until the selector has finished.

Auto-release pools are thread specific, so if we are creating auto-released objects on a new thread, we need to create an auto-release pool to release them.

[NSThread detachNewThreadSelector:@selector(Process) toTarget:self withObject:nil];

This calls the function Process on a different thread.

- (void) Process
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   NSMutableString* output = 
     [[[NSMutableString alloc] initWithString:@"batman!"] autorelease];
   NSLog(@"output: %@", output);
   [self performSelectorOnMainThread:@selector(FinishProcess) 
         withObject:nil waitUntilDone:false];
   [pool drain];
}

The object output is allocated and set to auto-release inside the auto-release pool, and will be released before the end of the function.

- (void) FinishProcess
{
   NSMutableString* output = 
     [[[NSMutableString alloc] initWithString:@"superman?"] autorelease];
   NSLog(@"output: %@", output);
}

There is an auto-release pool created for the main thread automatically, so in FinishProcess, we do not need to create an auto-release pool as this function runs on the main thread.

Summary

To avoid memory leaks in your iPhone apps, it is important to keep in mind who owns each object that is allocated, when to relinquish that ownership, and keeping retain and release calls in pairs. If you follow the rules of ownership, your apps will be more stable and you will cut down a lot of bug fixing time.


你可能感兴趣的:(ios,object,function,iPhone,reference,output)