I'm having unseen object leak (CFRuntimeCreateInstance) when profiling my app with instrument leak tool. I have rough imagination where the leak occurs but unable to spot it :) The call tree points me to my class method for loading an image from a spritesheet. Here is a fragment of the call tree (until leaking object CFRuntimeCreateInstance):
+[ImageCache loadImageOfType:andIndex:]
+[NSString stringWithFormat:]
_CFStringCreateWithFormatAndArgumentsAux
CFStringCreateCopy
__CFStringCreateImmutableFunnel3
_CFRuntimeCreateInstance
I'm using this helper class method for loading and caching the image. The method uses static NSMutableDictionary where it pushes loaded image for further use. My images are grouped into spritesheets, so the method reads corresponding image file and reads corresponding image area. Here is my method:
+(UIImage*)loadImageOfType:(int)type andIndex:(int)index{
UIImage *image;
if (!dict) dict = [[NSMutableDictionary dictionary] retain];
//First, trying to get already loaded image
int ind = IMAGECOUNT * type + index; //calculating the index that is used for storing image in dict
image = [dict objectForKey:[NSString stringWithFormat:#"%d", ind]]; //trying to get image
if (!image){ //if image is not loaded then read the spriteimage file
NSString *spritesheetName = [NSString stringWithFormat:#"itemsprite%d.png", type];
NSString* imagePath = [[NSBundle mainBundle] pathForResource:spritesheetName ofType:nil];
image = [UIImage imageWithContentsOfFile:imagePath];
if (image) //if spritesheet exists
{
int loopInd;
CGFloat x = 0, y = 0;
//load all images from it
for (int i=0; i<IMAGECOUNT; i++) {
UIImage *img;
loopInd = IMAGECOUNT * type + i;
CGImageRef imageRef = CGImageCreateWithImageInRect( [image CGImage], CGRectMake(x, y, ITEMIMAGESIZE, ITEMIMAGESIZE));
img = [UIImage imageWithCGImage:imageRef];
[dict setObject:img forKey:[NSString stringWithFormat:#"%d", loopInd]];
CGImageRelease(imageRef);
x += ITEMIMAGESIZE;
}
}
//set image var to be image needed
image = [dict objectForKey:[NSString stringWithFormat:#"%d", ind]];
}
return image;
}
Any suggestions, where to start?
UPDATE: I'm also having a lot of messages in debug area, all are quite similar:
__NSAutoreleaseNoPool(): Object 0x4c55e80 of class NSCFNumber autoreleased with no pool in place - just leaking
__NSAutoreleaseNoPool(): Object 0x4c69060 of class __NSCFDictionary autoreleased with no pool in place - just leaking
__NSAutoreleaseNoPool(): Object 0x4c6a620 of class NSCFString autoreleased with no pool in place - just leaking
__NSAutoreleaseNoPool(): Object 0x4e75fe0 of class NSPathStore2 autoreleased with no pool in place - just leaking
__NSAutoreleaseNoPool(): Object 0x4e312d0 of class UIImage autoreleased with no pool in place - just leaking
I found the problem. At the beginning of the game, I'm preloading the images. I have a method "loadGameResourses" that loops and does multiple calls to single image loading method "loadImageOfType...". Now, I'm performing the call of ""loadGameResourses" in separate thread:
[self performSelectorInBackground:#selector(loadGameResourses) withObject:nil];
The leaks just disappeared when I replaced that with:
[self loadGameResourses];
I heard that you need to deal with UIKit stuff in the main thread, so it looks like I have picked the wrong method. Everything works as expected when using this appropriate method:
[self performSelectorOnMainThread: #selector(loadGameResourses) withObject:nil waitUntilDone: NO];
I have heard that the instrument's leak tool can report false leaks. Have you tried opening Activity Monitor while your app is running and watching the memory? If there is a leak, the amount of memory will keep getting larger and larger. Make sure you actually have a leak before you dig too deep.
Related
I am building an app for iOS 5 using ARC and I seem to be having some memory issues. Basically its taking screen-shots of a portion of the display, placing the UIImage in an MSMutableArray and then piecing the screen-shots together for one big image. Now the problem is that after doing this a couple of times the OS closes the application due to high memory usage.
Here is the snippet that pieces the UIImage's together:
UIImage* finalImage = nil;
//join the screenshot images together
UIGraphicsBeginImageContext(CGSizeMake(collage.width, collage.height));
{
int hc = 0;
for(UIImage *img in imageArr)
{
NSLog(#"drawing image at:: %i", hc);
[img drawAtPoint:CGPointMake(0, hc)];
hc+=img.size.height;
img = nil;
}
//NSLog(#"creating finalImage");
finalImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
//do something with the combined image
//remove all the objects
[imageArr removeAllObjects];
//reset class instance
[self setImageArr: [[NSMutableArray alloc] init]];
Are they any other alternatives that I could use so there isn't so much memory being used? Maybe storing a CGImageRef in the array? Are there any potential memory leaks with the above code?
Any tips, pointers would be greatly appreciated.
Thanks.
[imageArr removeAllObjects]; will remove the objects from array. No need to reset the array again with
[self setImageArr: [[NSMutableArray alloc] init]];
By doing this you are allocating a NSMutableArray object and not releasing it.
Try by removing the line [self setImageArr: [[NSMutableArray alloc] init]];
make sure you alloc and init setImageArr
if (setImageArr == nil){
setImageArr = [[NSMutableArray alloc]init];
}
else
{
[setImageArr removeAllObjects];
}
or use (if you want to init from an existing Array):
NSMutableArray *setImageArr = [[NSMtableArray]initWithArray:arrayOfImages];
Because you say it will have memory issue after doing this a couple of times. Then how about you use NSAutoreleasePool to force system release objects after your method, example below:
#autoreleasepool {
UIImage* finalImage = nil;
//join the screenshot images together
UIGraphicsBeginImageContext(CGSizeMake(collage.width, collage.height));
{
int hc = 0;
for(UIImage *img in imageArr)
{
NSLog(#"drawing image at:: %i", hc);
[img drawAtPoint:CGPointMake(0, hc)];
hc+=img.size.height;
img = nil;
}
finalImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
//do something with the combined image
//remove all the objects
[imageArr removeAllObjects];
//reset class instance
[self setImageArr: [[NSMutableArray alloc] init]];
}
And I also doubt there is any memory leak problem in your other codes.
Using ARC doesn't meaning without memory leak problem, maybe you store many useless objects in a global variable etc.
Maybe you should use Instruments to monitor the memory to figure out where does the memory go.
Turns out the imageArr is being cleared properly. There appears to be a memory issue somewhere else in the program.
Instruments is saying there is a memory leak in this code:
- (void)layoutImageMaskViewForImageAtPath:(NSString *)path withFillColor:(UIColor *)color indexPath:(NSIndexPath *)indexPath {
UIImage *image = [UIImage imageWithContentsOfFile:path];
[self layoutImageMaskViewForImage:image withFillColor:color indexPath:indexPath];
}
UIColor *anIconFillColor = [UIColor colorWithWhite:0.70 alpha:1.0];
NSIndexPath *anIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSString *aPlaceholderPath = [[NSBundle mainBundle] pathForResource:#"path" ofType:#"png"];
[self layoutImageMaskViewForImage:anImage withFillColor:anIconFillColor indexPath:anIndexPath];
and
NSDictionary *anAssignedData = [aReservationData objectForKey:kAssignedSectionKey];
NSMutableArray *anEmployeeTaskQueueList = [NSMutableArray array];
NSArray *anAssignedReservationData = [anAssignedData objectForKey:kEmployeesIdentifier];
for (NSDictionary *aJobQueueData in anAssignedReservationData) {
EmployeeReservationQueue *anAssignedTaskQueue = [[EmployeeReservationQueue alloc] initWithServerDictionary:aJobQueueData];
if (anAssignedTaskQueue.rows.count == 0) {
ReservationTrack *aTrack = [[ReservationTrack alloc] init];
aTrack.rowSortOrder = 0;
aTrack.reservations = [NSArray array];
anAssignedTaskQueue.rows = [NSArray arrayWithObject:aTrack];
[aTrack release];
}
[anEmployeeTaskQueueList addObject:anAssignedTaskQueue];
[anAssignedTaskQueue release];
}
Your second example leaks track. Your last line is releasing aTrack instead.
In second case here:
[aTrack release];
What is aTrack? May be you mean [track release];?
In first case probably that you pass to function non-autoreleased parameters or may be you are not releasing them after calling that method. Just post code where you call for that method and I will check.
Gold memory-management rule in Objective-C :
Each 'init', 'copy','mutableCopy','retain' must call then 'release' or 'autorelease'.
Instruments reports that your app is leaking a ReservationTrack object. By default it shows where the leaked object was allocated, which is the code you posted. The code you posted doesn't leak a ReservationTrack. It stores it in an EmployeeReservationQueue which is stored in an NSMutableArray. One possibility is that you later access the ReservationTrack object, send it retain, and don't send it release or autorelease. Another possibility is that you leak the EmployeeReservationQueue or the NSMutableArray.
If you use the simulator, you can see the full retain/release history of most objects. When a leaked object shows up, mouse over the address of the object and click the right arrow that appears next to the address. Instruments will show you every malloc, retain, release, and autorelease event for that object. If you choose View > Extended Detail from the menu bar, you can click on any of those events and see the stack trace of the event. This should help you track down the unbalanced retain.
I'm implementing an image browser, using a UIScrollView. Due to memory costranints, I've to implement image dynamic loading (I don't want use CATiled Layers because it forces user remaining waiting to load every tile).
I've tried in a coupe of ways:
- (UIImageView*) ldPageView{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
NSError *error;
NSData *imData = [NSData dataWithContentsOfURL:ldPageRef options:NSDataReadingUncached error:&error];
UIImage *im = [[UIImage alloc] initWithData:imData];
ldView = [[UIImageView alloc] initWithImage:im] ;
[ldView setFrame:pageRect];
[pool release]; // Release the objects in the pool.
return ldView;
}
And even in this way
- (UIImageView*) ldPageView{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
CGDataProviderRef provider = CGDataProviderCreateWithURL ((CFURLRef)ldPageRef);
CGImageRef d = CGImageCreateWithJPEGDataProvider(provider,nil, true,kCGRenderingIntentDefault);
UIImage *im = [[UIImage alloc] initWithCGImage:d];
ldView = [[[UIImageView alloc] initWithImage:im] autorelease];
[im release];
CGDataProviderRelease(provider);
CGImageRelease(d);
[ldView setFrame:pageRect];
[pool release]; // Release the objects in the pool.
return ldView;
}
But every time I try it both on simulator and on iPad, memory explodes. I've runned my code with Instruments and no leak is reported. ldView is an istance variable and it is deallocated togheter with ldPageRef on object dealloc (which is called for sure).
I've also tried setting NSURLCache sharedCache to nil or to zero, but it is still happening.
I've read Memory management guide, but everythimg seems ok to me.
Please help
Try using
UIImage *im = [UIImage imageWithData:imData];
rather than
UIImage *im = [[UIImage alloc] initWithData:imData];
Always avoid allocs if possible otherwise you must ensure that you manually release the object.
More than likely it is how you are creating your UIImage. Try creating your image as such..
[UIImage imageWithData:imData];
instead of
[[UIImage alloc] initWithData:imData];
This will return a autoreleased object(it is a class method) so that you will not have to try to release it yourself later.
You are never releasing your alloc'd objects. You need to change:
[[UIImage alloc] initWithData:imData];
[[[UIImageView alloc] initWithImage:im];
to:
[[[UIImage alloc] initWithData:imData] autorelease];
[[[UIImageView alloc] initWithImage:im] autorelease] ;
Indeed I have found a memory leak in UIImageView. You just never pay any attention to it, since you may open images from the App package all the time, and these images are being cached by iOS.
But if you download a number of images from the network (say 40 iPhone-camera photos), save them to the documents and open them over and over again in a sub view controller, the memory leak applies. You do not need to have 40 different images, it is enough to load one image again and again.
Your test app is with ARC disabled and an image is being loaded from file and displayed in a sub view controller, every time that controller is being pushed.
In your sub view controller you'll create an UIImageView and assign the UIImage object to the image view's .image property. When you leave the sub view controller, you release the image view correctly. On an iPhone 4s your app won't open more than 80 images. Mine crashed at around 70 images (with iOS 6.1). And have a look on the intruments app, before the crash. The memory is full of CFMalloc blocks.
The solution I found is simple: Set the image property to nil, before you release the image view. Again, look at the instruments app. It now looks like what you'd expect and your app does no longer crash.
I think the same leak applies to whatever the UIWebView uses to display images and Apple doesn't know about it.
I'm getting images from UIGetScreenImage() and storing directly in mutable array like:-
image = [UIImage imageWithScreenContents];
[array addObject:image];
[image release];
I've set this code in timer so I cant use UIImagePNGRepresentation() to store as NSData as it reduces the performance. I want to use this array directly after sometime i.e after capturing 1000 images in 100 seconds. When I use the code below:-
UIImage *im = [[UIImage alloc] init];
im = [array objectAtIndex:i];
UIImageWriteToSavedPhotosAlbum(im, nil, nil, nil);**
the application crashes.
And I dont want to use UIImagePNG or JPGRepresentation() in timer as it reduces performance.
My problem is how to use this array so that it is converted into image.
If anybody has idea related to it please share with me.
You don't need to release the image in your first code sample there. [UIImage imageWithScreenContents] returns an autoreleased object.
1. UIImage *im = [[UIImage alloc] init];
2. im = [array objectAtIndex:i];
3. UIImageWriteToSavedPhotosAlbum(im, nil, nil, nil);
Line 1 allocates and initializes a new UIImage object, which never gets released since you overwrite the pointer on line 2. You are leaking an UIImage in each iteration, and you don't even need to init/alloc a new object.
UIImageWriteToSavedPhotosAlbum([array objectAtIndex:i], nil, nil, nil);
Should work just fine.
Also note Carl Norum answer about releasing an autoreleased object.
Im developing an app for an iPhone and I found that the following code is causing the memory allocation to increment.
-(UIImage *)createRecipeCardImage:(Process *)objectTBD atIndex:(int)indx
{
[objectTBD retain];
// bringing the image for the background
UIImage *rCard = [UIImage imageNamed:#"card_bg.png"];
CGRect frame = CGRectMake(00.0f, 80.0f, 330.0f, 330.0f);
// creating he UIImage view to contain the recipe's data
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = rCard;
[rCard release];
imageView.userInteractionEnabled = YES;
float titleLabelWidth = 150.0;
float leftGutter = 5.0;
float titleYPos = 25.0;
float space = 3.0;
float leftYPos = 0;
// locating Title label
float currentHeight = [self calculateHeightOfTextFromWidth:objectTBD.Title :titleFont :titleLabelWidth :UILineBreakModeWordWrap];
UILabel *cardTitle = [[UILabel alloc]initWithFrame:CGRectMake(leftGutter, titleYPos, titleLabelWidth, currentHeight)];
cardTitle.lineBreakMode = UILineBreakModeWordWrap;
cardTitle.numberOfLines = 0;
cardTitle.font = titleFont;
cardTitle.text = objectTBD.Title;
cardTitle.backgroundColor = [UIColor clearColor];
[imageView addSubview:cardTitle];
[cardTitle release];
leftYPos = titleYPos + currentHeight + space;
// locating brown line
UIView *brownLine = [[UIView alloc] initWithFrame:CGRectMake(5.0, leftYPos, 150.0, 2.0)];
brownLine.backgroundColor = [UIColor colorWithRed:0.647 green:0.341 blue:0.122 alpha:1.0];
[imageView addSubview:brownLine];
[brownLine release];
leftYPos = leftYPos + 2 + space + space + space;
// creating the imageView to place the image
UIImageView *processPhoto = [[UIImageView alloc] initWithFrame:CGRectMake(leftGutter, leftYPos, 150, 150)];
if((uniqueIndex == indx) && (uniqueImg.imageData != nil))
{
if([uniqueImg.rcpIden isEqualToString:objectTBD.iden])
{
objectTBD.imageData = [NSString stringWithFormat:#"%#", uniqueImg.imageData];
[recipesFound replaceObjectAtIndex:indx withObject:objectTBD];
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
[imageData release];
processPhoto.image = rcpImage;
[rcpImage release];
}
}
else if(objectTBD.imageData != nil)
{
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
processPhoto.image = rcpImage;
[rcpImage release];
[decodedBigImageDataPointers addObject:imageData];
}
else
{
UIImage * rcpImage = [UIImage imageNamed:#"default_recipe_img.png"];
processPhoto.image = rcpImage;
[rcpImage release];
}
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 1
[imageView addSubview:processPhoto];
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 2!!!!
//[processPhoto release]; // this line causes an error :(
// converting the UIImageView into a UIImage
UIGraphicsBeginImageContext(imageView.bounds.size);
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[objectTBD release];
for(UIView *eachSubview in imageView.subviews)
{
[eachSubview removeFromSuperview];
NSLog(#"each subview retainCount %i despues", [eachSubview retainCount]);
// here I find that the processPhoto view has a retain count of 2 (all other views have their retain count in 1)
}
return viewImage;
}
When I checked at the instruments object allocation I found that the "GeneralBlock-9216" growing up.
Breaking down the row I found that every time I call this code, one instance of:
2 0x5083800 00:18.534 ImageIO initImageJPEG
is being allocated. Checking the call stack, the following line is highlighted:
UIImage * objImage = [UIImage imageWithData:imageData];
Any help to find what the error is?
As TechZen said, the imageWithXXX: methods cache the image inside of them while you run the program (though you release the instances after using). I recommend initWithXXX: and release API sets instead of imageWithXXX:.
Well, if you embed several debug log on your source code, check how many times is it called, and check the retain count of the instances.
As far as I can explain, that is all.
I hope you will solve the problem.
Does anyone have an answer for this? It's tearing me apart trying to figure out why this image information keeps lingering. I've tried every solution.
The situation:
Images get downloaded and stored to the device, then loaded with imageWithContentsOfFile (or even initWithContentsOfFile, which doesn't help either). When the view goes away, the images don't, but they don't show up as leaks, they're just this initImageJPEG Malloc 9.00 KB that never goes away and keeps ramping up.
UPDATE: I believe I've figured this out: Check to make sure everything is actually getting dealloc'd when you're releasing whatever the parents (and/or grandparents) and etc of the images are. If the parents don't get deallocated, they never let go of their children images, and whatever data was in those images sticks around. So check retain counts of parent objects and make sure that everything's going away all the way up whenever you release the view at the top.
A good way to check for this is to put NSLogs into custom classes' dealloc methods. If they never show up, that object isn't going away, even though the reference to it might, and it (and whatever its subviews and properties are) will never ever disappear. In the case of images, this means a pretty sizable allocation every time that object is generated and never deallocated. It might not show up in leaks, especially if the parent of the topmost object you're thinking you're releasing but actually aren't persists and doesn't itself ever deallocate.
I hope this helps. It'll be useful to take some time to read through your code with a fine-toothed comb to make sure you're allocating and releasing things properly. (Search for "alloc]", start at the top of the file, and work your way down to make sure you're releasing and that the release isn't inside of some if() or something.)
Also, running "Build and Analyze" might lock up your machine for a bit, but its results can be really helpful.
Good luck!
I think you're seeing UIImage cacheing images. There used there used to be a method something like initWithData:cache that let you turn the cacheing off. Now I think the system always caches the images automatically behind the scenes even after you've deallocted the specific instances.
I don't think its an error on your part. I think it's the system keeping data around in the OpenGl subsystem. Unless it causes a major leak, I don't think it is a problem.