Can't release unused CALayer memory when using multiple layers - objective-c

I am using multiple CALayer's in my application with large UIImage as contents.
unfortunately, when I don't need the layer nor the image - the memory is not freed.
the code I use to create the layer is:
UIImage *im = [UIImage imageNamed:#"image_1.jpg"];
CALayer * l = [CALayer layer];
[l setBounds:CGRectMake(0, 0, 1024, 768)];
[l setPosition:CGPointMake(512, 384)];
[l setAnchorPoint:CGPointMake(0.5, 0.5)];
[l setHidden:NO];
[l setContents:(id) im.CGImage];
[self.layer addSublayer:l]; // self is a subclass of UIView
[self.tmpArr addObject:l]; // self.tmpArr contains the layers I am using (one in this example)
the code I use to release the layer and it's contents is :
CALayer * l = [self.tmpArr objectAtIndex:i];
[l removeFromSuperlayer];
[l setHidden:YES];
[l setContents:nil];
[self.tmpArr removeAllObjects];
when I'm using the instruments memory profiler I see the real memory increasing when creating the layer but never decreasing when freeing there.
I can't use release since I am using ARC. what do I do wrong here ?
Thanks.

UIImage's imageNamed: method uses a static cache that is only released upon tight memory situations.
Your options are:
Use -[UIImage imageWithContentsOfFile:] instead.
Ignore the problem. The cache is cleaned up when a memory notification comes in.

Just for documenting another answer. The image that I created in my code was drawn at runtime using CGImageCreateWithImageInRect. The produced object is CGImageRef. I simply forgot to release it using CGImageRelease causing the memory consumption to increase gradually and after 3 or 4 animation, the application crashes due to memory constraint.
Always release an CGImageRef.

Related

Draw several UIViews on one layer

Is it possible to draw several UIViews with custom drawing on single CALayer so that they dont have backing store each?
UPDATE:
I have several uiviews of the same size which have same superview. Right now each of them has custom drawing. And because of big size they create 600-800 mb backing stores on iPad 3. so i want to compose their output on one view and have several times less memory consumed.
Every view has it's own Layer and you can't change that.
You could enable shouldRasterize to flatten a view hierarchy, which might help in some cases, but that needs gpu memory.
another way could be to create an image context and merge the drawings into the image and set that as the layer content.
in one of the wwdc session videos of last year which was about drawing showed a drawing app where many strokes were transfered into a image to speed up drawing.
Since the views will share the same backing store, I assume you want them to share the same image that results from the layer's custom drawing, right? I believe this can be done with something similar to:
// create your custom layer
MyCustomLayer* layer = [[MyCustomLayer alloc] init];
// create the custom views
UIView* view1 = [[UIView alloc] initWithFrame:CGRectMake( 0, 0, layer.frame.size.width, layer.frame.size.height)];
UIView* view2 = [[UIView alloc] initWithFrame:CGRectMake( 100, 100, layer.frame.size.width, layer.frame.size.height)];
// have the layer render itself into an image context
UIGraphicsBeginImageContext( layer.frame.size );
CGContextRef context = UIGraphicsGetCurrentContext();
[layer drawInContext:context];
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// set the backing stores (a.k.a the 'contents' property) of the view layers to the resulting image
view1.layer.contents = (id)image.CGImage;
view2.layer.contents = (id)image.CGImage;
// assuming we're in a view controller, all those views to the hierarchy
[self.view addSubview:view1];
[self.view addSubview:view2];

UIGraphicsGetImageFromCurrentImageContext() retain cause crash

This is a weird one, but I think I'm just missing something simple.
I have an Objective C class called Theme.
Themes load all the art for my game from files and stores them in a NSMutableArray.
When the app launches it reates a new Theme object.
Part of the loading process does something like this:
UIImage *image = [self mergeImage: bg toImage: overlay];
[imageArray addObject: image];
I take a background image and place the overlay on top of it, creating a new UIImage, by using this code:
- (UIImage *)mergeImage:(UIImage *)image1 toImage:(UIImage *)image2 {
UIGraphicsBeginImageContext(image1.size);
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
[image2 drawInRect:CGRectMake(0, 0, image2.size.width, image2.size.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
//[resultingImage retain];
UIGraphicsEndImageContext();
return resultingImage;
}
All of this happens in the init function of Theme.
Here's the problem, when I call [[Theme alloc] init] from
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
The app crashes a second later, leaving me with no stack or anything(I figure it screws up some memory and crashes later when it uses it again or something).
Here's the weird part. If I don't add the newly merged image to the imageArray, it never crashes. I don't have the image anymore, but there's no crash. Also, if I don't add the image to the array, but I retain it in the mergeImage function, it does crash, which makes me think that retaining this newly-made image is causing the problem.
Weirder still, if I don't call mergeImage until later on in the app, outside of teh [Theme init] and didFinishLaunchingWithOptions, it's OK.
Any idea why? What's wrong with retaining this image when the app first launches?
Try this Below Code:
UIGraphicsBeginImageContext(drawImage.bounds.size);
[drawImage.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage
Thanks..!

How to refresh view in objective-c

I wanted to create a gallery. It loads different images based on the category, that a user selects. I used to populate images in UIImageViews.
My when selecting different categories is that it does not clear the previously selected images. This is my code for populating images.
-(void)refresh{
categoryLabel.text = treatment.treatmentName;
NSMutableArray *galleryImages =[NSMutableArray alloc] ;
galleryImages = [[PatientImage alloc]find:treatment.treatmentId];
int imgCount = [galleryImages count];
for(int i=0;i<imgCount;i++){
PatientImage *apatientImage = [galleryImages objectAtIndex:i];
UIImage *img1 = [UIImage imageNamed:apatientImage.imageBefore];
UIImageView *myImageView = [[UIImageView alloc] initWithImage:img1];
myImageView.contentMode = UIViewContentModeTopRight;
myImageView.frame = CGRectMake(120+i*240,120.0,100.0, 100.0);
[self.view addSubview:myImageView];
UIImage *img2 = [UIImage imageNamed:apatientImage.imageAfter];
UIImageView *myImageView2 = [[UIImageView alloc] initWithImage:img2];
myImageView2.contentMode = UIViewContentModeTopRight;
myImageView2.frame = CGRectMake(120+i*240+300,120.0,100.0, 100.0);
[self.view addSubview:myImageView2];
}
}
First things first, You have some memory leaks there. You are allocating UIImageViews but are not releasing them anywhere, after you have added them to your view. I don't know if that applies to ARC, though. Same applies to your Mutable array, but I suppose you are releasing it after the 'for' loop somewhere, since it seems you posted code after omitting some of it.
As far as your actual question is concerned, I wouldn't do this this way. I would make the mutable array an object variable, and then fill it with my image views. When calling refresh again, I would first call -removeFromSuperview on each image view, then empty the array, then repopulate it and add the new subviews to my view. That is the simple way.
I don't know if you are using ARC, but you should be careful about memory management when using dynamically loaded views. Each time you add a view to another one, you increase its retain counter. You must then call release to remove ownership, and let the iOS runtime handle the rest.
Also note that operations such as this using views are expensive in terms of memory. So, another way of repopulating the gallery view is to just change the image an imageView holds. That will save you some memory, and time. In case the view doesn't have a constant number of images to be displayed, you can refine your algorithm to change the images on the already created image views, and then add more image views if necessary or delete the remaining ones, if any.
I hope I helped.
try at the start of refresh call
[[self subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
or
for (id imageView in self.subviews){
if([imageView isKindOfClass:[UIImageView class]]) [imageView removeFromSuperview];
}
call [tableview reloadData] if You are using tableview to show your gallery images
or call view's
[self.view setNeedsDisplay] method for refreshing the view.

adding UIImageView in UIScrollview with large image blocks thread

I am loading an image in a UIImageView which I then add to a UIScrollView.
The image is a local image and is about 5000 pixels in height.
The problem is that when I add the UIImageView to the UIScrollView the thread is blocked.
It is obvious because when I do this I can not scroll the UIScrollView till the image is displayed.
Here's the example.
UIScrollView *myscrollview = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 768, 1004)];
myscrollview.contentSize = CGSizeMake(7680, 1004);
myscrollview.pagingEnabled = TRUE;
[self.view addSubview:myscrollview];
NSString* str = [[NSBundle mainBundle] pathForResource:#"APPS.jpg" ofType:nil inDirectory:#""];
NSData *imageData = [NSData dataWithContentsOfFile:str];
UIImageView *singleImageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:imageData]];
//the line below is the blocking line
[scrollView addSubview:singleImageView];
It is the last line in the script that blocks the scroller. When I leave it out everything works perfect, except for the fact the image is not showing of course.
I seem to recall that using multithreading does not work on UIView operations so I guess that's out of the question ass well.
Thanks for your kind help.
If you're providing these large images, you should maybe check out CATiledLayer; there's a video of a good presentation on how to use this from WWDC 2010.
If these aren't your images, and you can't downsample them or break them into tiles, you can draw the image on a background thread. You may not draw to the screen graphics context on any main thread but the main thread, but that doesn't prevent you from drawing to a non-screen graphics context. On your background thread you can
create a drawing context with CGBitmapContextCreate
draw your image on it just as you would draw onto the screen in drawRect:
when you're done loading and drawing the image invoke your view's drawRect: method on the main thread using performSelectorOnMainThread:withObject:waitUntilDone:
In your view's drawRect: method, once you've fully drawn your image on the in-memory context, copy it to the screen using CGBitmapContextCreateImage and CGContextDrawImage.
This isn't trivial, you'll need to start your background thread at the right time, synchronize access to your images, etc. The CATiledLayer approach is almost certainly the better one if you can find a way to manipulate the images to make that work.
Why you want to load such huge image to memory at one time? split it into many small images and load/free it dynamically.
Try allocating the UIImageView without a UIImage, and add it as a sub-view to the UIScrollView.
Load the UIImage in a separate thread, and get the method running on the other thread to set the image property of the UIImageView when the image is loaded into memory. Also you'll probably encounter some memory problems as an image of this size loaded into a UIImage will probably be 30MB+
UIScrollView *myscrollview = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 768, 1004)];
myscrollview.contentSize = CGSizeMake(7680, 1004);
myscrollview.pagingEnabled = TRUE;
[self.view addSubview:myscrollview];
NSString* str = [[NSBundle mainBundle] pathForResource:#"APPS.jpg" ofType:nil inDirectory:#""];
UIImageView *singleImageView = [[UIImageView alloc] init];
[scrollView addSubview:singleImageView];
//Then fire off a method on another thread to load the UIImage and set the image
//property of the UIImageView.
Just keep an eye on memory and beware of using convenience constructors with UIImage (or any object that could end up being huge)
Also where are you currently running this code?

imageWithCGImage and memory

If I use [UIImage imageWithCGImage:], passing in a CGImageRef, do I then release the CGImageRef or does UIImage take care of this itself when it is deallocated?
The documentation isn't entirely clear. It says "This method does not cache the image object."
Originally I called CGImageRelease on the CGImageRef after passing it to imageWithCGImage:, but that caused a malloc_error_break warning in the Simulator claiming a double-free was occurring.
According to the fundamental rule of Cocoa memory management, an owning object should release the owned object when it no longer needs it. Other objects are responsible for taking and releasing ownership on their own. If a UIImage needs an object to persist but doesn't retain or copy it, it's a bug in UIImage's implementation and should be reported as such.
I have the same problem in XCode 3.2.1 on Snow Leopard. I have pretty much the same code as Jason.
I have looked at the iPhone sample code which uses imageWithCGImage and they always release the CGImageRef using CGImageRelease after a call to imageWithCGImage.
So, is this a bug in the simulator? I always get the malloc_error_break warning on the console when I use CGImageRelease.
The ownership in UIImage about CGImage is unclear. It seems like don't copy the CGImage but it's not guaranteed by documentation. So we have to handle that ourselves.
I used a new subclass of UIImage to handle this problem. Just retaining passing CGImage, and releasing it when the dealloc.
Here is sample.
#interface EonilImage : UIImage
{
#private
CGImageRef sourceImage;
}
- (id)initWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation;
#end
#implementation EonilImage
- (id)initWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation
{
self = [super initWithCGImage:imageRef scale:scale orientation:orientation];
if (self)
{
sourceImage = imageRef;
CGImageRetain(imageRef);
}
return self;
}
- (void)dealloc
{
CGImageRelease(sourceImage);
[super dealloc];
}
#end
Because the CGImage returned by -[UIImage CGImage] property is not guaranteed to be same CGImage passed into init method, the class stores CGImage separately.
I agree with you -- the documentation is muddled at best for this API. Based on your experiences, then, I would conclude that you are responsible for the lifetime of both objects - the UIImage and the CGImageRef. On top of that you have to make sure the lifetime of the CGImageRef is at least as long as the UIImage (for obvious reasons).
Are you using Xcode 3.1 on Snow Leopard? I am experiencing the same issue:
CGContextRef ctx = ...;
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
UIImage * newImage = [[UIImage imageWithCGImage:cgImage] retain];
CGImageRelease(cgImage); // this should be OK, but it now crashes in Simulator on SL
I'm guessing that upgrading to Xcode 3.2 will fix the issue.
Not sure if this helps, but I had a similar problem.
I read the answers and then did the following which appears to have fixed it:
CGImageRef cgImage = [asset thumbnail];
UIImage *thumbImage = [[UIImage imageWithCGImage:cgImage ]retain];
UIImageView *thumbImageView = [[UIImageView alloc] initWithImage:thumbImage];
CGImageRelease(cgImage);
I used the autorelease as suggested but it was not enough.
One I had added the image to the UIImageView I then released the cgImage.
It didn't crash and deallocated nicely. Why -- I have no idea but it worked.
The asset thumbnail part is from the ALAsset Library by the way, you might need something else there.
<>
Not my opinion. I had the same problem. According to documentation
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
imho keeps all references correct. If you are using a CGDataProvider please have a look at
CGDataProviderReleaseDataCallback
and set a breakpoint in your callback. You can see that is is correctly called after you [release] your image and you can free() your image data buffer there.
"This method does not cache the image object."
So the UIImage take the ownership, and thus you should not release the CGImageRef.