views:

137

answers:

2

iPhoneOS 3.2

I use NSKeyedUnarchiver's unarchiveObjectWithFile: to load a custom object that contains a single large NSData and another much smaller object. The dealloc method in my custom object gets called, the NSData object is released, its retainCount == 1 just before. Physical memory does not decrement by any amount, let alone a fraction of the NSData size, and with repetition memory warnings are reliably generated: I have test until I actually received level 2 warnings. =(

NSString *archivePath = [[[NSBundle mainBundle] pathForResource:@"lingering"]
     ofType:@"data"] retain];
lingeringDataContainer = [[NSKeyedUnarchiver unarchiveObjectWithFile:archivePath] retain];
[archivePath release];
[lingeringDataContainer release];

and now the dealloc....

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

Before release:

(gdb) p (int) [(NSData *) lingeringData retainCount]
$1 = 1

After:

(gdb) p (int) [(NSData *) lingeringData retainCount]
Target does not respond to this message selector.

+1  A: 

First, you're retaining and releasing objects which do not need to have that happen to them. Here's the cleaned up code:

NSString *archivePath = [[NSBundle mainBundle] pathForResource:@"lingering"]
     ofType:@"data"]; // Do not retain again.
lingeringDataContainer = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath]; // Do not retain again.
// Do not release, because archivePath is already autoreleaed: [archivePath release];
// Again, this is already autoreleased: [lingeringDataContainer release];

Or more simply:

NSString *archivePath = [[NSBundle mainBundle] pathForResource:@"lingering"]
     ofType:@"data"];
lingeringDataContainer = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath]; 

Second, where's the rest of the code? It's probably something else which is being retained or cached somewhere else.

lucius
I need to retain the data container for future use. Look again at the gdb output and you will see that you are wrong about any caching.
ctpenrose
I tried to edit the comment above and took too long =( Anyway, I tried your "cleaned up" code, to be thorough as I was fairly confident that it would not give different results since the retain count was the same at the end of the code block as it was in mine, and the leak persists. There effectively isn't any other code -- it has been removed to isolate the problem. It looks like there is a bug in NSKeyedUnarchiver. It may be similar to:http://stackoverflow.com/questions/1250050/memory-leak-problem-using-nsdata-in-iphone
ctpenrose
Your dealloc should have a [lingeringDataContainer release] then. What is lingeringData? Is that a separate object or is it a typo? The question in your link points to a NSURLConnection. I don't know of any bug with NSKeyedArchiver. I use it a lot but I haven't checked its memory usage, so maybe it's caching the data. What is the byte size of the data?
lucius
No it should not. lingeringData is an NSData... I posted the dealloc for lingerDataContainer. An object doesn't call its own release method from within dealloc. The code example is small, but it doesn't need to be bigger since the data isn't used for anything.The only other code of interest perhaps are the encode/decode methods... they are very simple. The bytesize varies from 1-3mbs in my tests.
ctpenrose
I just noticed that setObjectZone: can be implemented by subclasses of NSCoder.. I would love to know that going this route would actually work (destroy the zone for each release) before I try it =)
ctpenrose
You never said the name of the class or that the instance was lingerDataContainer. Am I somehow supposed to read your mind? I can't really help you if you don't provide more context for your code.
lucius
My context could be clearer, however there is enough there to communicate my issue, particularly with the clarifications I have already posted. But I agree I don't think that you can help; this appears to be an Apple bug.
ctpenrose
A: 

When are you checking the memory usage? Is it just after the code snippet you posted? archivePath and lingeringDataContainer are both autoreleased. They will not be deallocated until (at least) the autorelease pool is drained (usually at the end of the current event). There may also be a lot of other internal stuff that has been autoreleased and won't go away until the pool is drained.

Try doing this:

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // <==
NSString *archivePath = [[[NSBundle mainBundle] pathForResource:@"lingering"]
 ofType:@"data"] retain];
lingeringDataContainer = [[NSKeyedUnarchiver unarchiveObjectWithFile:archivePath] retain];
[pool release];                                             // <==
[archivePath release];
[lingeringDataContainer release];
JeremyP
I tried creating my own autorelease pool to encapsulate the unarchiving, releasing it when finished, and the memory is still retained. I check the memory usage continuously with Instruments as I repeat the process of unarchiving objects. While some might argue that the memory isn't really occupied, the level 1 and level 2 warnings say that the memory retention is real.It is quite possible that NSKeyedUnarchiver does not use the local autorelease pool on iPhoneOS for some reason. unarchiveObjectWithFile: is a factory object call...
ctpenrose
Then I'm confused.The above snippet of code does not contain any leaks and neither did your original code, unless there is a leak in initWithCoder: Can you post that?
JeremyP