views:

1032

answers:

3

Hi,

I have a memory management-related question in a multithreaded iPhone application. Let's say we have this method, that is called in a separate thread than the main-UI-thread:

- (BOOL)fetchAtIndex:(NSUInteger)index
{
    NSURL *theURL = [NSURL URLWithString:[queryURLs objectAtIndex:index]];
    // Pay attention to this line:
    NSData *theData = [[NetworkHelper fetchFromNetwork:theURL] retain];

    // Some code here...

    // Now what should I do before returning result?
    //[theData release]; ??
    //[theData autorelease]; ??
    return YES;
}

As you can see, I'm retaining the NSData I got back from my network operation. The question is: why shouldn't I release (or autorelease) it at the end of my method? The only way I made it work is to use retain at first, and nothing then. If I use any other combination (nothing at all; retain then release or autorelease), my program crashes with EXC_BAD_ACCESS when I release the thread's NSAutoreleasePool. What am I missing?

FYI, here is the main code for the thread:

- (void)threadedDataFetching;
{
    // Create an autorelease pool for this thread
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // Reload data in separate thread
    [self fetchAtIndex:0];

    // Signal the main thread that fetching is finished
    [self performSelectorOnMainThread:@selector(finishedFetchingAll) withObject:nil waitUntilDone:NO];

    // Release all objects in the autorelease pool
    [pool release]; // This line causes EXC_BAD_ACCESS
}

Thanks for your help!

A: 

You must not release things you have not retained yourself (using either retain or implictly by methods with: init, new, or copy in their name).

If you retain result from fetchFromNetwork, then you must release it. Both release and autorelease should work (don't touch object after release, it's safest to set fields/variables to nil after release).

If you're not keeping the data, then you don't even need to retain it. [NetworkHelper fetchFromNetwork] should return autoreleased object. Body of fetchFromNetwork could look like this:

NSData *data = [[NSData alloc] init];
// stuff happens
return [data autorelease];

or

NSData *data = [otherObject dataFromOtherObject];
// stuff happens
return data; // don't (auto)release, since you haven't retained

If in doubt, err on the leaky side and run application via "Leaks" Instruments or LLVM checker.

porneL
Thank you.Indeed, fetchFromNetwork was like your second code sample:NSData *data = [otherObject dataFromOtherObject];...return [data autorelease]; // Wrong here!That's why I had to retain it in my client code.I removed the wrong -autorelease call, then the -retain code on the client side, and now everything's fine.Thanks!
Romain
A: 

The fact that you're on a separate thread is likely not relevant here. Memory management is the same, and you should be balancing your retain/release of NSData just as if this were the main thread. The fact that you're crashing when draining the autorelease pool suggests that you've probably done something with theData that you haven't showed us here (which seems obvious since the code as shown isn't very useful). What are you doing with theData that is requiring someone else to retain it?

Rob Napier
You're right, as I wrote in the other comment, fetchFromNetwork was autoreleasing the NSData object so I _had_ to retain it in this code so it worked, but the buggy part was in the fetchFromNetwork method.Thanks.
Romain
A: 

As others have noted, it's not actually relevant that it's multithreaded in this instance. It's worth reading up on the Objective C Memory Management Guidelines that Apple produces.

Roughly speaking, any objects that you explicitly [[Foo alloc] init] yourself, you should own the responsibility for cleaning up. If you're returning this value from a method, you need to autorelease it as well.

Any object you get from a caller that doesn't begin with initWith (such as [Foo emptyFoo]) it's the responsibility of the caller to own the resource (i.e. autorelease it).

If you get passed an object and want to keep it outside of the method call (i.e. save it to an instance field) you must retain it. When you're done, you release it, usually in the destructor. If you're using Objective C properties, defining it as @retain will kick in this behaviour for you automatically; in the destructor, you can just set the property to nil.

AlBlue