views:

146

answers:

6

I'm fairly new to iPhone development and I've hit a roadblock in my understanding of memory management. I've read through the Memory Management Guide for Cocoa and have read many, many questions and answers on SO, but have not found a complete answer.

If I have an instance method that creates an object, every example I've seen seems to use an autorelease call:

-(NSArray *)findThings {
    NSArray* things = [[[NSArray alloc] init] autorelease];
    // add some lovely things to my shiny new array
    return things;
}

Forgetting the contrived example, everything I've read about iPhone dev best practices says that autorelease pools are discouraged, but how can I implement the above example without one? If this method is called many, many times, it feels like I run the risk of clogging the iPhone's autorelease pool with, well, "things", which seems to go against the need to keep resource use to a minimum on such a constrained platform.

I considered the following:

-(NSArray *)findThings {
    NSArray* things = [[NSArray alloc] init];
    // add some lovely things to my shiny new array
    [things release];
    return things;
}

But then 'things' would have a retain count of zero before it passes to the calling method, so I feel like there's a significant risk of the object being deallocated between the call to [things release] and the method which calls findThings actually using the result.

I was a bit confused by the rule from the Memory Management Guide which states:

A received object is normally guaranteed to remain valid within the method it was received in. (...) That method may also safely return the object to its invoker.

I wasn't sure whether this meant as the writer of an instance method I could safely perform the release without the risk of the object being deallocated until the calling method's scope ended, or whether as a user of classes in the frameworks provided by apple I could assume that I didn't have to worry about retain/release/etc for objects I received from those classes as long as the method didn't have new/init/alloc/copy/whatever in its name.

So to summarise,

  • Can I use release before returning an object instead of autorelease to avoid using an autorelease pool on the iPhone?
  • If not, is there a better pattern for this that doesn't involve the autorelease pool?
  • Am I missing something fundamental here? It seems like there's a hole in the docs.

Thanks in advance

+3  A: 

Well autorelease is there to cover the exact scenarios that you are having problem with.

stefanB
Yes, but this doesn't actually address the substance of my question at all.
Shabbyrobe
+1  A: 

You follow the Create Rule.

Frank Shearar
THE RULE!!! MUST OBEY THE RULE!!
CVertex
In the land of manual memory management, it's what saves your bacon!
Frank Shearar
+8  A: 

Forgetting the contrived example, everything I've read about iPhone dev best practices says that autorelease pools are discouraged

Autorelease pools are not discouraged; they're used everywhere in Cocoa, and when used correctly they have zero impact on memory usage. Autorelease pools are only a concern when an they artificially extend the life of a large number of objects, say in a loop, in which case you would manage your autorelease pools manually.

Darren
According to this (http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html) they are, but perhaps I was just taking it too literally: "iPhone OS Note: Because on iPhone OS an application executes in a more memory-constrained environment, the use of autorelease pools is discouraged in methods or blocks of code (for example, loops) where an application creates many objects. Instead, you should explicitly release objects whenever possible."
Shabbyrobe
The key phrase is "loops where an application creates many objects". In a loop you want to either manually release temporary objects, or manage your own autorelease pool.
Darren
A: 

save yourself the hassle.. just do:

- (NSArray *)findThings{
      NSArray *things = [NSArray array];
      return things;
}

as stefanB mentioned earlier, autorelease is here to cover the problem you are having here. another solution could be that you just retain it, and release it where you use it.. but this could cause leaking if you are not carefull.

Antwan van Houdt
The example used in the question probably only used an array to illustrate the problem at hand. Just because NSArray has a convenience method for creating a new, autoreleased instance, doesn't mean other classes do. And in the case of creating new instances of classes that don't have convenience methods, then the alloc/init/autorelease/return path is the best way to go.
Jasarien
A: 

As everyone else is saying, this problem is what autorelease was designed for.

When creating and returning objects from an instance method, the object should be auto released before being returned.

This forces the invoker to follow correct memory management rules without going the extra mile to ensure the fact.

When a sender is returned an object from a method that doesn't contain the words Create, Alloc, Retain or New, then the result is expected to be autoreleased by convention. This lets the programmer know, easily, that the object should be retained if they want to keep it around, or that its safe to use and forget about within the current scope, without having to worry about its memory.

Jasarien
+2  A: 

Yes, you're right autorelease pools are discouraged for iPhone development when they're not absolutely necessary. They are discouraged because it allows objects to stay in memory longer then necessary this is a problem when developing applications for the iPhone, where memory is scarce. Autoreleased objects don't get released until it makes it back to the main autorelease pool created in main.c and this could be a long time. (unless you created your own autorelease pool, I'll talk about this last.)

A case where an autoreleased object is not necessary:

NSString *string = [NSString stringWithString:@"hello world"];
//use string

The first case is more convenient since I don't have to worry about releasing string later but it could have been easily been replaced with:

NSString *string = [[NSString alloc] initWithString:@"hello world"];
// use string
[string release];

For these cases avoiding the autoreleased object is recommended.


When you're passing back objects using autorelease is unavoidable.

You must use:

-(NSArray *)findThings {
    NSArray* things = [[[NSArray alloc] init] autorelease];
    // add some lovely things to my shiny new array
    return things;
}

Since the object is invalid as soon as you call release.

-(NSArray *)findThings {
    NSArray* things = [[NSArray alloc] init];
    // add some lovely things to my shiny new array
    [things release];
    // things is now in invalid object if you could potentially use things later it should be set to nil to avoid a crash
    things = nil; // sending messages to nil is okay, it won't cause a crash, sending messages to a released object is not okay, it can cause unpredictable crashes.        
    return things;
}

In cases where you're using a large amount of autoreleased memory you should create and drain your own autorelease pool so the autorelease memory doesn't stay around longer then necessary.

-(void)useLotsOfAutoReleasedObjects {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    // lots of autoreleased object
    // large autoreleased objects
    [pool drain];  // autorelease objects are released too
}

[pool drain] also releases the pool, see the NSAutoreleasePool Reference for more information.

Hua-Ying
Add, as an addendum to the last example, if you're in a loop (say, parsing lots of records from a CSV or XML document), you would create the pool before the loop, release the pool after the loop, and after each pass of the loop call [pool drain]: http://developer.apple.com/mac/library/documentation/cocoa/reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html#//apple_ref/occ/instm/NSAutoreleasePool/drain
Sixten Otto
@Sixten Otto: As indicated by your link, `drain` is identical to `release`. So using a pool after draining it is not at all kosher. You need to create a new one.
Chuck
I don't know what the heck I was thinking. I apparently manufactured a memory of a way to pop the pool without deallocating it. :-/
Sixten Otto