Huge memory leak in NSBitmapImageRep - objective-c

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?

Related

Issues with threading, blocks, CGImageRef and scope

I have a program where I set up a completion block that is running on a background thread. Inside the block I set up a CGImageRef and then on the main thread set my layer contents. THe problem is, sometimes the app crashes on the main thread portion.
This is the completion block, in the code below both the fullImage and cfFullImage are declared in my .h
requestCompleteBlock completionBlock = ^(id data)
{
// Seems I need to hold onto the data for use later
fullImage = (NSImage*)data;
NSRect fullSizeRect = NSMakeRect(0, 0, self.frame.size.width, self.frame.size.height);
// Calling setContents with an NSImage is expensive because the image has to
// be pushed to the GPU first. So, pre-emptively push it to the GPU by getting
// a CGImage instead.
cgFullImage = [fullImage CGImageForProposedRect:&fullSizeRect context:nil hints:NULL];
// Rendering needs to happen on the main thread or else crashes will occur
[self performSelectorOnMainThread:#selector(displayFullSize) withObject:nil waitUntilDone:NO];
};
The final line of my completion block is to call displayFullSize. That function is below.
- (void)displayFullSize
{
[self setContents:(__bridge id)(cgFullImage)];
}
Do you see or know of any reason why the setContents might fail?
Thanks
Joe
cgFullImage is not retained. The CGImage Core Foundation object is deallocated and you are using a deallocated object.
Core Foundation object pointer types like CGImageRef are not managed by ARC. You should either annotate the instance variable with __attribute__((NSObject)), or change the type of the instance variable to an Objective-C object pointer type like id.

iOS Application Crash while UIImage loading (virtual memory not cleaning up)

I have a weird crash in my application without any trace. this is probably a memory related problem but with very little information & I'm not sure how to proceed or fix it. If it wasnt for instruments would have been left with no clue what so ever.
I have an image array (in this example an array of size 2) where I load an image, create an image context & draw and save it into the array. Everytime the method is called image array objects are replaced with the new content. In instruments I see a very huge Virtual Memory usage during this method call & apparently after each call memory is not cleared & hence crashes. The project is ARC. I'll list down code below. This is all we need to recreate this issue. (the image I'm using is little big in size about 7MB, so its easier to recreate crash). Also i'm using iPad2 device.
+ (UIImage *)imageCopy:(UIImage *)src
{
UIGraphicsBeginImageContext(src.size);
[src drawAtPoint:CGPointZero];
UIImage *r = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return r;
}
- (IBAction)buttonTouch:(id)sender
{
for (int i=0; i<2; i++)
{
if (i==0)
{
self.mImage = [UIImage imageNamed:#"LARGE_elevation.jpg"];
}
else
{
self.mImage = [UIImage imageNamed:#"LARGE_elevation2.jpg"];
}
// imageArray is a NSMutableArray with capacity of 2
[imageArray setObject:[ViewController imageCopy:self.mImage] atIndexedSubscript:i];
}
((UIImageView *)[self.view viewWithTag:100]).image = self.mImage;
}
Here is a screen from instruments where it crash on 2nd time after memory warnings are issued.
I dont see any big issue with the "imageCopy" method I'm using here.
Any help on this is really appreciated.
Thanks & Cheers,
I found out that it was a cyclic reference issue. So when new content replaces the old content in the array, the past objects were still remaining. It was quite an interesting finding because in the memory leaks analyser it showed up as few KB data leak which you wont suspect as the non freed virtual memory was few hundred Megabytes (MB).
As a very abstract example.
ClassA
#property (strong) ClassB *obj
----------
ClassB
#property (strong) ClassA *obj
- (id)initWithA:(ClassA *)objA;
----------
So when you remove A neither object will be deallocated properly. In my case leak traced by the leak analyser was few KB for both of the objects even though the CoreGraphics calculations were hanging onto about 200MB data in virtual memory.
Fix was to mark the A reference in ClassB as weak.
ClassB
#property (weak) ClassA *obj
- (id)initWithA:(ClassA *)objA;
Verdict
Never under estimate a memory leak, no matter how big or small & arc or mrc
The problem is probably that the method imageNamed: caches the images loaded, and there is apparently no way to clear the cache after a memory warning programmatically.
Instead of imageNamed:, you could use other methods like initWithData: that do not cache the images. You will find a detailed discussion here.

Objects not initialized

in objective C, I can do the following
UIImage *myImage = [UIImage imageNamed:#"myPhoto.jpg"];
variable.image = myImage;
and this works just fine. but the object named "myImage" was never initialized, and the UIImage never had any memory allocated and yet the code still works..
Can someone explain what's going on here?
Yes, the object was initialised. The imageNamed: method allocates and initialises an object, sends it an autorelease message, then returns the memory address to you. You store that memory address in the pointer called myImage.
myImage and the object are two different things. myImage merely points at a memory location. It is not the object itself.
You can pass around objects without assigning them to variables, and you can assign one object to many variables.
Consider this:
UIImage *imageOne;
UIImage *imageTwo;
imageOne = [UIImage imageNamed:#"myPhoto.jpg"];
imageTwo = imageOne;
The image wasn't copied. There is only one object in existence. Both variables point to it.
Now consider this:
NSLog(#"%#", [UIImage imageNamed:#"myPhoto.jpg"]);
You didn't assign it to any variable. But the object still existed, right?
Have a look in the documentation for UIImage. Under the heading "Cached Image Loading Routines" and "Creating New Images" are some methods with a + where you might expect a -. This means that calling these methods is the same as calling [[[UIImage alloc] init] autorelease];, though some of them do more (as in custom initialization).
Lots of other Objective C objects have similar methods, for example, NSArray has a method +(id)array that creates and returns an empty array. Your main indicator here is the + instead of the -, but I always check the documentation to check how the initialization is handled and to make sure the object is autoreleased.

objc leak behavior I can't explain

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.

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);