I have a subclass of UIImageView view that loads pdf image data, so that I can have a resolution independent graphic in my view. Works well for the stated purpose, but I am getting memory leaks with this, according to an instruments leaks profile.
Here is the code below that I believe should be responsible for the leaks. I am trying to track down the problem, but I am a little foggy on how to pinpoint the issue.
- (id)initWithPDFResourceAtPath:(NSString *)path center:(CGPoint)center {
if ((self = [super init])){
CGPDFPageRelease(pageRef);
CGPDFDocumentRef documentRef = CGPDFDocumentCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:path]);
pageRef = CGPDFDocumentGetPage(documentRef, 1);
CGPDFPageRetain(pageRef);
CGPDFDocumentRelease(documentRef);
[self setBounds];
}
return self;
}
-(void)setBounds {
[self setBounds:CGRectApplyAffineTransform(CGPDFPageGetBoxRect(pageRef, kCGPDFMediaBox), CGAffineTransformMakeScale(scaleH, scaleV))];
size = self.bounds.size;
[self getPDFimage];
}
-(void)getPDFimage {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleH, scaleV);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextSetRenderingIntent(context, kCGRenderingIntentDefault);
CGContextDrawPDFPage(context, pageRef);
[self setImage:UIGraphicsGetImageFromCurrentImageContext()];
}
You forgot to call UIGraphicsEndImageContext(). Change your code to:
UIImage *image = [self setImage:UIGraphicsGetImageFromCurrentImageContext()];
UIGraphicsEndImageContext();
return image;
EDIT1: your code has this pageRef variable - is it an ivar or a static? If an ivar you better release it with CGPDFPageRelease() in the dealloc method. [It really should be an ivar]
EDIT2: See attached screen shot on Object Alloc. You can see the type and current amount and its ordered from most to least.
EDIT3: all else fails create a demo project that has the same problem and post it on Dropbox.
EDIT4: Code was uploaded to: here (I cannot look at it til May 28th)
EDIT5: The problem is that pageRef is not ever released. So:
1) remove this from your init method, as it does nothing:
CGPDFPageRelease(pageRef);
2 and move it to a new dealloc method:
- (void)dealloc
{
CGPDFPageRelease(pageRef);
}
Related
so i am trying to make an app that will let the user change the color of the UIImage, for that i am using this function i found
- (UIImage *)imageWithTintColor:(UIColor *)color fraction:(CGFloat)fraction
{
if (color)
{
UIImage *image;
if ([UIScreen instancesRespondToSelector:#selector(scale)])
{
UIGraphicsBeginImageContextWithOptions([self size], NO, 0.f);
}
else
{
UIGraphicsBeginImageContext([self size]);
}
CGRect rect = CGRectZero;
rect.size = [self size];
[color set];
UIRectFill(rect);
[self drawInRect:rect blendMode:kCGBlendModeDestinationIn alpha:1.0];
if (fraction > 0.0)
{
[self drawInRect:rect blendMode:kCGBlendModeSourceAtop alpha:fraction];
}
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
return self;
}
everything works but the CG raster Data is growing in memory
I found the problem, and it was my bad logic, i am using 2 views one to show and one to work with ex:resize, move, rotate. And each time i was addingSubview to both where one of them need to hold just 1 at a time, a simple:
for (UIView *view in 2cndView.subviews)
{
[view removeFromSuperview];
}
did the trick for me
I have been fighting with my app, that suddenly would not launch properly, for some time now. It turned out that when I had switched a number of images' Render as to Template in the Image asset file, it caused the app to totally bomb out. CG Raster Data was growing exponentially and finally caused the app to stop and Xcode just said
Lost connection with iPhone.. check connections etc
It would appear that during every launch the images get reprocessed for this 'Template' setting, which consumed a disgusting amount of RAM and actually left it unable to boot. To solve this, I lowered the resolution of the images - as simple as that.
I've searched a lot but only found two methods to take screen shot of UIView.
first renderInContext:
I've used it in a way
CGContextRef context = [self createBitmapContextOfSize:CGSizeMake(nImageWidth, nImageHeight)];
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, nImageHeight);
CGContextConcatCTM(context, flipVertical);
[self.layer setBackgroundColor:[UIColor clearColor].CGColor];
[self.layer renderInContext:context];
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage* background = [UIImage imageWithCGImage: cgImage];
CGImageRelease(cgImage);
Second drawViewHierarchyInRect: which I've used as
UIImage *background = nil;
UIGraphicsBeginImageContextWithOptions (self.bounds.size, NO, self.window.screen.scale);
if ([self respondsToSelector:#selector(drawViewHierarchyInRect:afterScreenUpdates:)])
{
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
}
background = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I know that the second one is faster than first and it work for me for iPhone because the view has low size. but when I capturing from iPad the video become jerky.
Can Any body tell me faster way of taking screen shot.
any help would be highly appreciated
Regarding performance, the Apple Docs state the following:
In addition to -drawViewHierarchyInRect:afterScreenUpdates:, UIView
now provides another two snapshot related methods,
-snapshotViewAfterScreenUpdates: and -resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:. UIScreen also has -snapshotViewAfterScreenUpdates:.
Unlike UIView's -drawViewHierarchyInRect:afterScreenUpdates:, these
methods return a UIView object. If you are looking for a new snapshot
view, use one of
these methods. It will be more efficient than calling
-drawViewHierarchyInRect:afterScreenUpdates: to render the view contents into a bitmap image yourself. You can use the returned view
as a visual stand-in for the current view/screen in your app. For
example, you might use a snapshot view for animations where updating a
large view hierarchy might be expensive.
There is a third method for taking a snapshot that is much much quicker than either of these but it returns a UIView.
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates
If you are just using the snapshot to place as a background "image" etc... then I'd use this instead.
However, this is only available for iOS8.
To use it just do...
UIView *snapshotView = [someView snapshotViewAfterScreenUpdates:YES];
This Method will return you A snapshot images of particular view
-(UIImage *)createSnapShotImagesFromUIview
{
UIGraphicsBeginImageContext(CGSizeMake(view.frame.size.width,view.frame.size.height));
CGContextRef context = UIGraphicsGetCurrentContext();
[mapView.layer renderInContext:context];
UIImage *img_screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img_screenShot;
}
In my iPhone app, I have a large image that I've cached to disk and I retrieve it just before I hand the image to a class that does a lot processing on that image. The receiving class only needs the image briefly for some initialization and I want to release the memory that the image is taking up as soon as possible because the image processing code is very memory intensive, but I don't know how.
It looks something like this:
// inside viewController
- (void) pressedRender
{
UIImage *imageToProcess = [[EGOCache globalCache] imageForKey:#"reallyBigImage"];
UIImage *finalImage = [frameBuffer renderImage:imageToProcess];
// save the image
}
// inside frameBuffer class
- (UIImage *)renderImage:(UIImage *)startingImage
{
CGContextRef context = CGBitmapCreateContext(....)
CGContextDrawImage(context, rect, startingImage.CGImage);
// at this point, I no longer need the image
// and would like to release the memory it's taking up
// lots of image processing/memory usage here...
// return the processed image
CGImageRef tmpImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *renderedImage = [UIImage imageWithCGImage:tmpImage];
CGImageRelease(tmpImage);
return renderedImage;
}
This may be obvious, but I'm missing something. Thank you.
#Jonah.at.GoDaddy is on the right track, but I would make all of this more explicit rather than relying on ARC optimizations. ARC is much less aggressive in debug mode, and so your memory usage may become too high when you're debugging unless you take steps.
UIImage *imageToProcess = [[EGOCache globalCache] imageForKey:#"reallyBigImage"];
First, I'm going to assume that imageForKey: does not cache anything itself, and does not call imageNamed: (which caches things).
The key is that you need to nil your pointer when you want the memory to go away. That's going to be very hard if you pass the image from one place to another (which Jonah's solution also fixes). Personally, I'd probably do something like this to get from image->context as fast as I can:
CGContextRef CreateContextForImage(UIImage *image) {
CGContextRef context = CGBitmapCreateContext(....)
CGContextDrawImage(context, rect, image.CGImage);
return context;
}
- (void) pressedRender {
CGContextRef context = NULL;
// I'm adding an #autoreleasepool here just in case there are some extra
// autoreleases attached by imageForKey: (which it's free to do). It also nicely
// bounds the references to imageToProcess.
#autoreleasepool {
UIImage *imageToProcess = [[EGOCache globalCache] imageForKey:#"reallyBigImage"];
context = CreateContextForImage(imageToProcess);
}
// The image should be gone now; there is no reference to it in scope.
UIImage *finalImage = [frameBuffer renderImageForContext:context];
CGContextRelease(context);
// save the image
}
// inside frameBuffer class
- (UIImage *)renderImageForContext:(CGContextRef)context
{
// lots of memory usage here...
return renderedImage;
}
For debugging, you can make sure that the UIImage is really going away by adding an associated watcher to it. See the accepted answer to How to enforce using `-retainCount` method and `-dealloc` selector under ARC? (The answer has little to do with the question; it just happens to address the same thing you might find useful).
you can autorelease objects right away in the same method. I think you need to try to handle the "big-image" process within one methods to use #autorelease:
-(void)myMethod{
//do something
#autoreleasepool{
// do your heavy image processing and free the memory right away
}
//do something
}
I have following method to take a pdf file in documents directory and create a thumbnail out of it. This method is leaking memory in two places as shown in comments. Since I am using ARC I am not sure why is it leaking memory. How can I solve this.
+ (UIImage*)createPdfThumbnail:(NSString*)pdfFilePath {
NSURL *targetURL = [NSURL fileURLWithPath:pdfFilePath];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)targetURL); // 3.0% of memory leak
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);//for the first page
CGRect aRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
UIGraphicsBeginImageContext(aRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, aRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, -(aRect.origin.x), -(aRect.origin.y));
CGContextSetGrayFillColor(context, 1.0, 1.0);
CGContextFillRect(context, aRect);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, aRect, 0, false);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext(); // 97% of memory leak
CGContextRestoreGState(context);
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdf);
return thumbnail;
}
EDIT:
-(void)fromJSON:(NSDictionary *)JSON{
[super fromJSON:JSON];
self.path = JSON[#"path"];
//Create and save thumbnail
if (self.parentSpecSheet != nil){
#autoreleasepool {
UIImage* thumbnail = [Utilities createPdfThumbnail:self.path];
Photo* thumbnailPhoto = [Photo addObject];
[thumbnailPhoto setDelta:#(0)];
[thumbnailPhoto setImage:thumbnail];
[thumbnailPhoto.file setDelta:#(0)];
self.parentSpecSheet.thumbnail = thumbnailPhoto;
}
}
}
Two thoughts:
I experience a significant leak from CGContextDrawPDFPage when I test your code in iOS5 (and if you search for "CGContextDrawPDFPage leak", you'll see tons of references to permutations of this problem). This appears to be a known problem.
I see no appreciable leak in iOS 6 from the above code, though.
If you're still seeing this leak in iOS 6, then I suspect the problem does not rest in the above code. Do you have any other leaks reported? I'd also suggest you confirm that the object that owns this thumbnail is successfully getting deallocated itself (e.g. log/breakpoint in its dealloc method).
Unfortunately, when you look at the leaks tool, it's reporting where the leaked object was instantiated, not where the leak took place. You might want to confirm that the owner of this thumbnail is not, somehow, maintaining a strong reference to it (e.g., the owner, itself, has a retain cycle, or something like that).
I'm trying to create previews images of pages in a PDF
but I have some problems with the release of memory.
I wrote a simple test algorithm that cycles on the problem,
the app crashes near the 40th iteration:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:#"myPdf.pdf"];
CFURLRef url = CFURLCreateWithFileSystemPath( NULL, (CFStringRef)pdfPath, kCFURLPOSIXPathStyle, NO );
CGPDFDocumentRef myPdf = CGPDFDocumentCreateWithURL( url );
CFRelease (url);
CGPDFPageRef page = CGPDFDocumentGetPage( myPdf, 1 );
int i=0;
while(i < 1000){
UIGraphicsBeginImageContext(CGSizeMake(768,1024));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,CGRectMake(0, 0, 768, 1024));
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, 1024);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
// --------------------------
// The problem is here (without this line the application doesn't crash)
UIImageView *backgroundImageView1 = [[UIImageView alloc] initWithImage:UIGraphicsGetImageFromCurrentImageContext()];
// --------------------------
UIGraphicsEndImageContext();
[backgroundImageView1 release];
NSLog(#"Loop: %d", i++);
}
CGPDFDocumentRelease(myPdf);
The above-mentioned line seems to generate a memory leak,
however, instruments doesn't show memory problems;
Can I escape from this kind of mistake?someone can explain me in which way?
Are there other ways to show previews of a pdf?
UPDATE
I think the problem isn't the release of UIImage created by the method UIGraphicsGetImageFromCurrentImageContext() but the release of UIImageView created with this autorelease image.
I have divided the line of code in three steps:
UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIImageView *myImageView = [[UIImageView alloc] init];
[myImageView setImage: myImage]; // Memory Leak
The first and second lines doesn't create memory leaks so I think that the method UIGraphicsGetImageFromCurrentImageContext is not the problem.
I also tried as follows but the problem persists:
UIImageView *myImageView = [[UIImageView alloc] initWithImage:myImage];
I think there is a memory leak in the release of a UIImageView that contains a UIImage with the autorelease property.
I tried to write my object UIImageView inheriting a UIView as explained in this thread.
This solution works but isn't very elegant, it's a workaround, I would prefer to use the object UIImageView solving the memory problem.
The problem is this:
UIGraphicsGetImageFromCurrentImageContext()
returns an autoreleased UIImage. The autorelease pool holds on to this image until your code returns control to the runloop, which you do not do for a long time. To solve this problem, you would have to create and drain a fresh autorelease pool on every iteration (or every few iterations) of your while loop.
I know it's an old question, but I've just been banging my head against the wall on this for a few hours. In my app repeatedly calling
UIImage *image = UIGraphicsGetImageFromCurrentImageContext()
in a loop does hold on to the memory despite me calling image = nil; Not sure how long the app would keep hold of the memory before freeing, but it's certainly long enough for my app to get a memory warning then crash.
I managed to solve it finally by wrapping the code that calls / uses the image from UIGraphicsGetImageFromCurrentImageContext() in #autoreleasepool. So I have:
#autoreleasepool {
UIImage *image = [self imageWithView:_outputImageView]; //create the image
[movie addImage:image frameNum:i fps:kFramesPerSec]; //use the image as a frame in movie
image = nil;
}
Hope that might help someone.
For future reference here's what I did to solve this (tested in Swift 4).
I was calling the function below for every new image downloaded from the internet (on a utility queue). Before implementing the autorelease pool it would crash after processing about 100.
For simplicity, in the resizeImage function I've removed needed code except for the autoreleasepool and the part that was leaking.
private func resizeImage(image: UIImage, toHeight: CGFloat) -> UIImage {
return autoreleasepool { () -> UIImage in
[...]
let newImage = UIGraphicsGetImageFromCurrentImageContext() //Leaked
UIGraphicsEndImageContext()
return newImage!
}
}
I hope this helps!
For those who tried all solution above and still has a memory leak, check if you are using a dispatch queue. If so, be sure to set its autoreleaseFrequency to .workItem. Or the autorelease pool you set up inside the will not execute.
DispatchQueue(label: "imageQueue", qos: .userInitiated, autoreleaseFrequency: .workItem)
Hope it helps, it has bugged me for hours until I finally realize that's DispatchQueue that is holding the block.
Is this code running on the main thread? The documentation of the UIGraphicsGetImageFromCurrentImageContext (link) says it must run that way.
your line of crash you can update it like following
get one UIimage out of loop
rendered_image = UIGraphicsGetImageFromCurrentImageContext();