objc leak behavior I can't explain - objective-c

The following code loop does not leak memory (as verified by watching it loop infinitely under "top");
NSBitmapImageRep *this_bmap = 0;
while (1) {
CGImageRef windowImage =
CGWindowListCreateImage(CGRectNull,
kCGWindowListOptionIncludingWindow,
windowID, kCGWindowImageDefault);
this_bmap = [[NSBitmapImageRep alloc] initWithCGImage:windowImage];
[this_bmap release];
CGImageRelease(windowImage);
}
and I would not expect it to. However, when I copy a pointer to the bitmap data, like this:
NSBitmapImageRep *this_bmap = 0;
while (1) {
CGImageRef windowImage =
CGWindowListCreateImage(CGRectNull,
kCGWindowListOptionIncludingWindow,
windowID, kCGWindowImageDefault);
this_bmap = [[NSBitmapImageRep alloc] initWithCGImage:windowImage];
void *pixels1 = [this_bmap bitmapData];
[this_bmap release];
CGImageRelease(windowImage);
}
this now leaks like crazy. I can see this happening rapidly under "top" and the program eventually grinds to a halt.
I am new to Objective-C, but I am not new to programming and I can't understand this behavior. The documentation for the method bitmapData claims it simply returns a pointer (as opposed to allocating something), so I am stumped. I found a similar question from some time ago, but the only answer was to "look into pools" and I don't see how that could help here.
Any ideas what's going on here?

Accessing the pixel data causes the object to be retained and autoreleased so that the bitmap data doesn't suddenly go away unexpectedly. To see your expected results (i.e., loop not consuming memory with each iteration), rewrite as:
NSBitmapImageRep *this_bmap = 0;
while (1) {
NSAutoreleasePool* loopPool = [NSAutoreleasePool new];
CGImageRef windowImage =
CGWindowListCreateImage(CGRectNull,
kCGWindowListOptionIncludingWindow,
windowID, kCGWindowImageDefault);
this_bmap = [[NSBitmapImageRep alloc] initWithCGImage:windowImage];
void *pixels1 = [this_bmap bitmapData];
[this_bmap release];
CGImageRelease(windowImage);
[loopPool drain];
}

hmmm, I'm no guru so I'm just guessing here. But it occurs to me that every time through to loop you are effectively declaring a new variable called pixels1. Therefore each time through you allocate some new space rather than reuse it. Try moving the declaration of pixels1 outside the loop and see what happens.
Note, this is just a guess and may be wrong. A more knowledgable person may be able to give you a definitive answer.

The "initWithCGImage:" call has this in its documentation:
Discussion
If you use this method, you
should treat the resulting bitmap
NSBitmapImageRep object as read only.
Because it only retains the value in
the cgImage parameter, rather than
unpacking the data, accessing the
pixel data requires the creation of a
copy of that data in memory. Changes
to that data are not saved back to the
Core Graphics image.
That seems to indicate that the memory returned from bitmapData needs to be CFRelease'ed.

Related

Custom Encoding with NSData, initializes but crashes

I've spent some serious time debugging this problem and I am stumped.
I'm trying to pack custom objects tightly into NSData to save space when they are saved to disk. I have implemented custom encoding and decoding methods for these custom objects. When I pack and unpack the objects, everything is initialized properly according to the debugger. Immediately after the code runs, it has either an EXC_BAD_ACCESS code=1 or code=2.
Here is the encoding and decoding:
-(instancetype) initWithData:(NSData *)data {
self = [super init];
if (self) {
_memConstants = [[DWMemoryConstants alloc]init];
int arraySize = _memConstants.chunkSize * _memConstants.chunkSize;
NSData* unencodedBlocks[arraySize];
[data getBytes:unencodedBlocks];
_blocks = [[NSMutableDictionary alloc]init];
for (int i = 0; i < 10; i++) {
DWBlock *block = [[DWBlock alloc]initWithData:unencodedBlocks[i]];
[_blocks setObject:block forKey:[NSValue valueWithCGPoint:CGPointFromString(block.blockName)]];
if (i == 0) {
_position = CGPointFromString(block.chunkName);
}
}
_chunkName = NSStringFromCGPoint(_position);
_bounds = CGRectMake(_position.x * _memConstants.chunkSize * kBlockSpriteWidth,
_position.y * _memConstants.chunkSize * kBlockSpriteWidth,
_memConstants.chunkSize * kBlockSpriteWidth,
_memConstants.chunkSize * kBlockSpriteWidth);
}
return self;
}
-(NSData *) customEncode {
NSMutableData *data = [[NSMutableData alloc]init];
NSData* blocks[_blocks.allValues.count];
int count = 0;
for (DWBlock *block in _blocks.allValues) {
blocks[count] = [block customEncode];
count++;
}
[data appendBytes:&blocks length:sizeof(blocks)];
return data;
}
The exception happens at the end of this code. It successfully prints all of the log messages first, and I have verified that there is nothing wrong with the Chunk object:
-(void) testEncodeDecode {
DWChunk *testChunk = [[DWChunk alloc]initWithPosition:CGPointMake(100, 100)];
NSData *testData = [testChunk customEncode];
NSLog(#"datasize= %#", testData);
DWChunk *unencodedChunk = [[DWChunk alloc]initWithData:testData];
NSLog(#"blocks=%#", unencodedChunk.blocks.allValues);
NSLog(#"this message will print");
}
As I said, all of the variables for the Chunk and Block objects are properly initialized. I suspect that the problem is related to improper releasing of objects, but I don't know where to go from here.
Edit: I have enabled checking for zombies, and now I get this error message:
2014-10-24 11:38:31.775 DigWorld[10622:60b] *** -[NSConcreteMutableData release]: message sent to deallocated instance 0x81790710
EDIT:
My initial thoughts where technically incorrect (read the code too fast...), but the actual error is along the exact same lines. By saving blocks, you save only the array of pointers, and not the actual data. You can make sure of that: in your comment you say that sizeof(blocks)=3600 and _blocks.allValues.count=900, so you only save the 900 pointers (* 4 bytes/pointer = 3600 bytes)
The actual data as created by [block customEncode] is then disposed of by ARC.
When reading this array afterward, you get pointers to pieces of memory (formerly NSData) that have been marked as freed, producing your crash, and explaining the error message when enabling zombies.
The tricky part: since this is the same instance of the program, and encode and decode are run next to each other, the actual memory has not been reallocated/overwritten. So you get the impression the objects are correctly saved. But rest assured this memory will eventually be overwritten by something else. You can make sure of that by saving the NSData on disk and decoding it on a re-run of the program.
It seems to me that you are trying to reinvent the wheel here.
The NSCoding protocol has been created exactly for that purpose: persisting graphs of objects on disk. The NSKeyedArchiver class will give you serialisation to disk with flexibility, and platform-independance, in a well-tested and documented interface.
You should be leveraging that architecture. Even if you decide that NSKeyedArchiver creates archives too "big" for your purposes, use the architecture in place and create your own archiver.
If you choose to do so, you can get inspiration with this open source NSCoder subclass implementation (disclaimer: I wrote it)

Is this a memory leak (using NSArray and NSCopying)

I tried something out in my code to see the effect on memory utilization. I wanted to find out if the line inside the loop was leaking. Running this loop took utilization up to 100MB and it didn't go back down again. Does this indicate a memory leak? If so why? (I'm using ARC)
for (i = 0; i < 10000000; i++)
{
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
(accounts is an array of AccountSummary objects which implements NSCopying like this: name city state phone are all NSStrings, isLocal is BOOL)
- (id)copyWithZone:(NSZone *)zone {
AccountSummary *newAccount = [[AccountSummary allocWithZone:zone] init];
newAccount.name = [self.name copyWithZone:zone];
newAccount.city = [self.city copyWithZone:zone];
newAccount.state = [self.state copyWithZone:zone];
newAccount.phone = [self.phone copyWithZone:zone];
newAccount.isLocal = self.isLocal;
return newAccount;
}
There's no leak here that I can see. There will, however, be quite a bit of peak memory usage.
The exact behaviour of things that should logically release memory varies. Quite often, instead of releasing the memory it's autorelased. (With MRR, there used to be a method called autorelease.) When you autorelease something, it isn't really released but is instead scheduled for release later, when your code is finished because it's returned to the main event loop.
If part of this is being autoreleased — and my guess is that the property assignment is autoreleasing, because autorelease is "safer" than hard releasing — that memory won't be deallocated until your next autoreleasepool flush. Code on the main thread has an autoreleasepool set up by the OS itself, so each time you return to the main event loop everything that's been autoreleased gets flushed out. Here, that probably means that all 10,000,000 copies are kept in memory until you return to the main event loop. Darn right that'll crash a real device. :)
(That's assuming you're on the main thread; if you're not, you may not even have an autorelasepool set up, which means you probably will get a leak. But I think you get warnings to console in this case, so you'd already have a hint about which way to go.)
You can reduce this peak memory usage by using #autoreleasepool:
for (i = 0; i < 10000000; i++) #autoreleasepool {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
What will happen now is that the memory scheduled for release later in each iteration of the loop will actually be released each iteration of the loop. That should solve your immediate problem.
That said, i can't imagine why you're doing this except to check the behaviour. And if that's the case, this is unlikely your core problem.
Assuming your core problem is a leak, with ARC you're not really looking for leaks. You're looking for circular references. That means your answer likely lies elsewhere in your code. If you're sure it's self's accounts rather than dsl's accounts that are leaking, look for self being involved in a circular loop.
Also, keep in mind that calling copyWithZone: on a NSString will probably not copy the string. (There's no need to copy a read-only string, as the read-only string can't be changed. Both "copies" can be the same object.) So if you're leaking just strings, they could be associated with the original objects.
When creating lots of objects inside a loop, you should do that inside an auto release pool.
#autoreleasepool {
for (i = 0; i < 10000000; i++) {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
}
or, more likely in the real world...
for (i = 0; i < 10000000; i++) {
#autoreleasepool {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
}

Heap growth related to use of CCLabelTTF

I am using Cocos2d 1.01. I am having undesired heap growth. To identify what is causing the growth I took a baseline snapshot, did a state change and return to zero state and repeated the heapshot. I found the primary cause of the heap growth to be non-object and then looked at the stack trace, noting that the problematic code appeared to be centered around CCLabelTTF.
Here is the code that seems to be problematic:
NSString *desc = [pEffectDescriptions objectAtIndex:i];
CCLabelTTF *descrptionLabel = [CCLabelTTF labelWithString:desc dimensions:CGSizeMake(290, 65) alignment:UITextAlignmentLeft fontName:#"Verdana-Italic" fontSize:10];
descrptionLabel.anchorPoint = ccp(0,0);
descrptionLabel.color = ccc3(192, 192, 192);
descrptionLabel.position = ccp(aSprite.position.x + 8, aSprite.position.y);
[self addChild:descrptionLabel z:10 tag:COMPARTMENT0+9600+i];
I don't understand what the problem is, because before returning to state zero, the following code is executed:
for (int i=0; i<1000; i++) {
if ([self getChildByTag:COMPARTMENT0+9000+i])
[self removeChildByTag:COMPARTMENT0+9000+i cleanup:true];
}
My reasoning is that the CCLabelTTF is owned by the layer and it in turn owns the NSString (the array also retains the NSString). However, when I remove the CCLabelTTF from the layer and its dealloc gets called it should therefore release the CCLabelTTF, which would then dealloc. Could the array reference to the NSString be responsible for preventing the deallocation of CCLabelTTF?
Anyone have a clue?
While adding labels you add 9600 to the tag, while removing you only add 9000 to the tag. Could that be it?
PS: I suggest using bitmap fonts, they use less memory, create, update and render faster.

Problem filling NSMutableArray w/ UIImage objects

I am having a baffling issue while trying to fill an NSMutableArray with UIImages.
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderMask;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
while(...) // <--- Iterates through data sets
{
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, data, numBytes, NULL);
CGImageRef cImage = CGImageCreate(iw, ih, 8, 32, 4 * iw, colorSpace, bitmapInfo, provider, NULL, NO, renderingIntent);
UIImage *finalImage = [UIImage imageWithCGImage:cImage];
[images addObject:finalImage]; // <--- Declared and instantiated earlier
[delegate sendImage:finalImage];
CGImageRelease(cImage);
CGDataProviderRelease(provider);
}
CGColorSpaceRelease(colorSpace);
[delegate operationCompletedWithImages:images];
That is how I have the code running. So I basically have a function running in the while statement that returns the next set of bitmap data, I then create a UIImage and store it into the mutable array.
I've tested this by writing each file to disk and then accessing them which results in the proper set of images. The problem is when using the array to keep data in memory, accessing any image object in the array I get the same exact image over and over. The image is also always the last image of the set.
I've tried testing the contents by setting the array as a UIImageView animation array and by using an NSTimer to cycle the contents. Either way it is just the same image (last image pulled) over and over.
I have this operation running inside a subclassed NSOperation object so it doesn't block the interface. Another interesting aspect here is that when the images array sent with operationCompletedWithImages was giving me the array of duplicate images I tried using the sendImage message and store the images in a different array inside the delegate object (thinking maybe it was a threading issue). This gave me the same results.
I've been stuck on this for over a week with no progress. I've never seen anything like it and I can't find anyone else who has had a similar issue.I would be happy to provide extra information if someone feels it would assist in solving this issue.
If anyone could provide any assistance I would greatly appreciate it.
Not sure if this is the problem, but you should probably be notifying your delegate of the changes on the main thread, not the thread that the NSOperation is running on.
[delegate performSelectorOnMainThread:#selector(sendImage:) withObject:finalImage waitUntilDone:YES];
And likewise for the last message. If the delegate is modifying anything UIKit-related, it must be invoked on the main thread!
One might assume that the NSMutableArray would handle the memory management of the object being added to it, but perhaps because you're dealing with the C level Quartz helpers/ Core wrappers this may not be the case.
In other image manipulating methods I've experimented with they are usually wrapped in an autorelease pool if they are involved with threads.
Have you tried experimenting with release/autorelease on the finalImage?
Figured out the problem. Turned out to be an issue with the backing data pointer.
Where before I was accessing data everytime thinking it would iterate and overwrite with new contents, and the new contents would be plugged into my CGDataProviderRef. This was not the case, I basically ended up supplying a pointer to the same backing data for the Provider. The solution was to copy the data into an NSData object and use that copy for the DataProvider.
So instead of:
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, data, numBytes, NULL);
It would be:
NSData *pxData = [[NSData alloc] initWithBytes:pFrameRGB->data length:numBytes];
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)pxData);

Huge memory leak in NSBitmapImageRep

I have a method that analyzes pixel data inside an NSBitmapImageRep that is built from a CGImageRef. Here is the relevant code:
CGImageRef ref;
// omitted code for initializing ref
NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:ref];
uint32* bitmapPixels = (uint32*) [bitmapRep bitmapData];
// do stuff with bitmapPixels
[bitmapRep release];
CGImageRelease(ref);
I know I'm properly releasing the CGImageRef and NSBitmapImageRep, but the call to -bitmapData leaks about 2 MB each time it's called, and I don't know how to properly release it. Any ideas?
Update: I forgot to add one important point: memory is only leaked when there is a full screen application running. For regular usage, the memory is released just fine.
Are you doing this in a loop? If so you might need to make an autorelease pool to make sure memory is cleaned up in a timely fashion. See autorelease pools.
The bitmap data should be owned by either the CGImage or the NSBitmapImageRep (or copied to an autoreleased behind-the-scenes NSData object by the latter). As such, whichever object owns (or copies) it should release it.
Are you seeing contrary results in heap/Instruments?