现在的位置: 首页 > 综合 > 正文

Practical Memory Management

2013年11月14日 ⁄ 综合 ⁄ 共 11390字 ⁄ 字号 评论关闭

Practical Memory Management

This article provides a practical perspective on memory management. It covers the fundamental concepts described in “Object Ownership and Disposal,”but from a more code-oriented perspective.

Following a few simple rules can make memory management easy. Failure to adhere to the rules will almost certainly lead at some point to memory leaks, or runtime exceptions due to messages being sent to freed objects.

Basics

To keep memory consumption as low as possible in an application, you should get rid of objects that are not being used, but you need to make sure that you don’t get rid of an object that is being used. You therefore need a mechanism that allows you to mark an object as still being useful. In many respects, memory management is thus best understood in terms of “object ownership.”

  • An object may have one or more owners.

    By way of an analogy, consider a timeshare apartment.

  • When an object has no owners, it is destroyed.

    To stretch the analogy, consider a timeshare complex that is not loved by the local population. If there are no owners, the complex will be torn down.

  • To make sure an object you’re interested in is not destroyed, you must become an owner.

    You can either build a new apartment, or take a stake in an existing one.

  • To allow an object in which you’re no longer interested to be destroyed, you relinquish ownership.

    You can sell your timeshare apartment.

To support this model, Cocoa provides a mechanism called “reference counting” or “retain counting.” Every object has a retain count. An object is created with a retain count of 1. When the retain count drops to 0, an object is deallocated (destroyed). You manipulate the retain count (take and relinquish ownership) using a variety of methods:

alloc

Allocates memory for an object, and returns it with retain count of 1.

You own objects you create using any method that starts with the word alloc or with the word new.

copy

Makes a copy of an object, and returns it with retain count of 1.

If you copy an object, you own the copy. This applies to any method that contains the word copy where “copy” refers to the object being returned.

retain

Increases the retain count of an object by 1.

Takes ownership of an object.

release

Decreases the retain count of an object by 1.

Relinquishes ownership of an object.

autorelease

Decreases the reference count of an object by 1 at some stage in the future.

Relinquishes ownership of an object at some stage in the future.

The practical rules for memory management are as follows (see also “Memory Management Rules”):

  • You only own objects you created using a method whose name begins with “alloc” or “new” or contains “copy” (for example, allocnewObject, ormutableCopy), or if you send it a retain message.

    Many classes provide methods of the form +className... that you can use to obtain a new instance of the class. Often referred to as “convenience constructors”, these methods create a new instance of the class, initialize it, and return it for you to use. You do not own objects returned from convenience constructors, or from other accessor methods.

  • Once you have finished with an object that you own, you should relinquish ownership using release or autorelease.

    Typically you should use release rather than autorelease. You use autorelease only when immediate deallocation of the object would be inappropriate, for example you are returning an object from a method. (Note: this does not imply that release necessarily results in deallocation of an object—it will only do so if the retain count drops to 0 as a result—but it might do, and sometimes you need to guard against that: see“Returning Objects from Methods” for an example.)

  • Implement a dealloc method to release the instance variables you own.

  • You should never invoke dealloc directly (other than when you invoke super’s implementation in a custom dealloc method).

Simple Examples

The following simple examples illustrate the contrast between creating a new object using alloc, using a convenience constructor, and using an accessor method.

The first example creates a new string object using alloc. It must therefore be released.

- (void)printHello {
    NSString *string;
    string = [[NSString alloc] initWithString:@"Hello"];
    NSLog(string);
    [string release];
}

The second example creates a new string object using a convenience constructor. There is no additional work to do.

- (void)printHello {
    NSString *string;
    string = [NSString stringWithFormat:@"Hello"];
    NSLog(string);
}

The third example retrieves a string object using an accessor method. As with the convenience constructor, there is no additional work to do.

- (void)printWindowTitle {
    NSString *string;
    string = [myWindow title];
    NSLog(string);
}

Using Accessor Methods

Sometimes it might seem tedious or pedantic, but if you use accessor methods consistently the chances of having problems with memory management decrease considerably. If you are using retain and release on instance variables throughout your code, you are almost certainly doing the wrong thing.

Consider a Counter object whose count you want to set.

@interface Counter : NSObject {
    NSNumber *count;
}

To get and set the count, you define two accessor methods. (The following examples present a simple perspective on accessor methods. They are described in greater detail in “Accessor Methods.”) In the get accessor, you just pass back a variable so there is no need for retain or release:

- (NSNumber *)count {
    return count;
}

In the set method, if everyone else is playing by the same rules you have to assume the new count may be disposed of at any time so you have to take ownership of the object—by sending it a retain message—to ensure it won’t be. You must also relinquish ownership of the old count object here by sending it a release message. (Sending a message to nil is allowed in Objective-C, so this will still work if count hasn't yet been set.) You must send this after [newCount retain] in case the two are the same object—you don't want to inadvertently cause it to be deallocated.

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [count release];
    // make the new assignment
    count = newCount;
}

The only places you shouldn’t use accessor methods to set an instance variable are in init methods and dealloc. To initialize a counter object with a number object representing zero, you might implement an init method as follows:

- init {
    self = [super init];
    if (self) {
        count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

To allow a counter to be initialized with a count other than zero, you might implement an initWithCount: method as follows:

- initWithCount:(NSNumber *)startingCount {
    self = [super init];
    if (self) {
        count = [startingCount copy];
    }
    return self;
}

Since the Counter class has an object instance variable, you must also implement a dealloc method. It should relinquish ownership of any instance variables by sending them a release message, and ultimately it should invoke super’s implementation:

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

Implementing a reset method

Suppose you want to implement a method to reset the counter. You have a couple of choices. The first uses a convenience constructor to create a newNSNumber object—there is therefore no need for any retain or release messages. Note that both use the class’s set accessor method.

- (void)reset {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
}

The second creates the NSNumber instance with alloc, so you balance that with a release.

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}

Common Mistakes

The following sections illustrate common mistakes.

Accessor not used

The following will almost certainly work correctly for simple cases, but tempting as it may be to eschew accessor methods, doing so will almost certainly lead to a mistake at some stage (when you forget to retain or release, or if your memory management semantics for the instance variable change).

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [count release];
    count = zero;
}

Note also that if you are using key-value observing (see Key-Value Observing Programming Guide), then changing the variable in this way is not KVO-compliant.

Instance leaks

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
}

The retain count of the new number is 1 (from alloc) and is not balanced by a release within the scope of the method. The new number is unlikely ever to be freed, which will result in a memory leak.

Instance you don’t own is sent release

- (void)reset {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
    [zero release];
}

Absent of any other invocations of retain, this will fail the next time you access count after the current autorelease pool has been released. The convenience constructor method returns an autoreleased object, so you don't have to send another release. Doing so will mean that when the releasedue to autorelease is sent, it will reduce the retain count to 0, and the object will be freed. When you next access count you will be sending a message to a freed object (typically you'll get a SIGBUS 10 error).

Cases which Often Cause Confusion

Using Collections

When you add an object to a collection such as an array, dictionary, or set, the collection takes ownership of it. The collection will relinquish ownership when the object is removed from the collection or when the collection is itself released. Thus, for example, if you want to create an array of numbers you might do either of the following:

NSMutableArray *array;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
    [array addObject:convenienceNumber];
}

In this case, you didn’t invoke alloc, so there’s no need to call release. There is no need to retain the new numbers (convenienceNumber), since the array will do so.

NSMutableArray *array;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger: i];
    [array addObject:allocedNumber];
    [allocedNumber release];
}

In this case you do need to send allocedNumber a release message within the scope of the for loop to balance the alloc. Since the array retained the number when it was added by addObject:, it will not be deallocated while it’s in the array.

To understand this, put yourself in the position of the person who implemented the collection class. You want to make sure that no objects you’re given to look after disappear out from under you, so you send them a retain message as they’re passed in. If they’re removed, you have to send a balancingrelease message, and any remaining objects should be sent a release message during your own dealloc method.

Returning Objects from Methods

When you return a local variable from a method, you must ensure both that you adhere to the memory management rules, and that the receiver gets an opportunity to use the object before it’s deallocated. When you return a newly-created object (that you own), you should relinquish ownership usingautorelease rather than release.

Consider a simple fullName method that concatenates firstName and lastName. One possible correct implementation (from the memory management perspective—it still leaves much to be desired from a functional perspective) would be as follows:

- (NSString *)fullName {
    NSString *string = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
    return string;
}

Following the basic rules, you don’t own the string returned by stringWithFormat, so you can safely return it from the method.

The following implementation is also correct:

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@", firstName, lastName] autorelease];
    return string;
}

You own the string returned by alloc, but you then send it an autorelease message, thereby relinquishing ownership before you lose the reference to it and satisfying the memory management rules again. It’s important, thought, to appreciate that this implementation uses autorelease rather thanrelease.

By contrast, the following is wrong:

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@", firstName, lastName] release];
    return string;
}

Purely from the perspective of memory management, it looks correct: you own the string returned by alloc, and send it a release message to relinquish ownership. From a practical perspective, however, in all likelihood the string is also deallocated at that stage (it’s unlikely to have any other owners), so the method’s caller will receive an invalid object. This shows why autorelease is useful in allowing you to defer release until some point in the future.

For completeness, the following is also wrong:

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@"%@ %@", firstName, lastName];
    return string;
}

You own the string returned by alloc, but lose the reference to it before you get a chance to relinquish ownership. Following the memory management rules, this would result in a memory leak, since the caller has no indication that they own the returned object.

 

 

From: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447

抱歉!评论已关闭.