When I analyze my app with Instruments, I found out that data allocated by CGContextDrawPDFPage is not released immediately. Since my program receives a lot of 'memory warnings' I would like to release as much memory as possible but I don't know how to release this memory.
As you can see on http://twitpic.com/473e89/full it seems to be related to this code
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx{
NSAutoreleasePool * tiledViewPool = [[NSAutoreleasePool alloc] init];
CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0);
CGContextFillRect(ctx, CGContextGetClipBoundingBox(ctx));
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform([self.superview.superview getPage],kCGPDFMediaBox,tiledLayer.bounds, 0, true);
CGContextSaveGState (ctx);
CGContextTranslateCTM(ctx, 0.0, tiledLayer.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextConcatCTM (ctx, pdfTransform);
CGContextClipToRect (ctx, CGPDFPageGetBoxRect([self.superview.superview getPage],kCGPDFMediaBox));
CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);
CGContextDrawPDFPage(ctx,[self.superview.superview getPage]);
CGContextRestoreGState (ctx);
UIGraphicsEndPDFContext();
[tiledViewPool drain];
}
I have already tried to wrap an AutoReleasePool around it but this does not seem to have any influence. The screenshot is taken after TiledView (the view where the method belongs to) is deallocated.
I hope someone can help me to reduce memory usage.
Everyone I know who had to deal with PDFs on iOS at some point has had this same problem.
The only solution seems to be to release the document and re-create it.
Note that the other common problem is that while you're releasing your document, a CATiledLayer may be rendering a page from that document at the same time. So, you should make good use of #synchronize (probably using your pdfDocRef) and release the document only when the tiled layer has finished its work.
Also, check this out: Fast and Lean PDF Viewer for iPhone / iPad / iOs - tips and hints?
I had a similar problem,
this line which gets a pdf page
[self.superview.superview getPage]
make sure you re-open the pdf every time you pull out a page, turns out CGPDFDocument does handle memory very well.
eg. something like
//release the current pdf doc
if(self.pdfDocumentRef!=nil){
CGPDFDocumentRelease(self.pdfDocumentRef);
}
//open it again
self.pdfDocumentRef=CGPDFDocumentCreateWithURL(..your url..);
//pull out a page
CGPDFPageRef pg = CGPDFDocumentGetPage(self.pdfDocumentRef, pageIndex+1);
Related
CGContextRef imageContext =
CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGContextDrawPDFPage( imageContext, page1 );
This code works fine in another project. But when I try to use it in another CGContextDrawPDFPage get stuck in an endless loop. I see in Instruments that syscall_thread_switch and _spin_lock is called in the loop.
Tried to call this in both main thread and a background thread. (Original app uses this in a background thread)
Has anyone else run in to this issue?
I had to instance an UILabel with the custom font, before drawing, to get it working.
I have an app that is drawing lines on a quartz context. The app starts drawing when the user move his finger across the screen.
At the time TouchesMoved is fired, I save the quartz context to a PNG file (I know saving a file is slow... I have tried to do this to memory but app memory usage skyrocketed, so, I am trying to do it to disk).
As the context is being saved to this, I do this on touches moved
if (firstMove) // first movement after touchesbegan
[NSThread detachNewThreadSelector:#selector(newThreadUNDO)
toTarget:self
withObject:nil];
firstMove = NO
}
and then I have
- (void) newThreadUNDO {
NSAutoreleasePool* p = [[NSAutoreleasePool alloc] init];
[NSThread setThreadPriority:0.1];
[NSThread sleepForTimeInterval:0.0];
[self performSelectorOnMainThread:#selector(copyUNDOcontext) withObject:nil waitUntilDone:NO];
[p release];
}
and
- (void) copyUNDOcontext {
CGFloat w = board.image.size.width;
CGFloat h = board.image.size.height;
CGRect superRect = CGRectMake(0,0, w, h);
CGSize size = CGSizeMake(w, h);
UIGraphicsBeginImageContext(size);
CGContextRef new = UIGraphicsGetCurrentContext();
// lineLayer is the layer context I need to save
CGContextDrawLayerInRect(new, superRect, lineLayer);
UIImage *imagem = UIGraphicsGetImageFromCurrentImageContext();
[self saveTempImage:imagem :#"UNDO.png"];
UIGraphicsEndImageContext();
}
The problem is: as soon as the user starts moving, the new thread is fired, but even this new thread being with low priority the main thread still freezes for about half second (probably while the file is being saved).
Why is that?
How can I try to solve that?
thanks.
Have you tried:
performSelector:onThread:withObject:waitUntilDone:
With waitUntilDone set to NO.
If I recall correctly performing the selector on the Main thread always processes the selector in the main run loop of the application. I could be wrong. I have been using GCD for some time now.
If you try this I believe you will need to put the autorelease pool into the function, as it will serve as the entry and exit point of the thread.
First, a method named like saveTempImage:: is to be discouraged. Make it saveTempImage:fileName: or something.
Your guess is probably good; saving the file is probably where the pause is coming from. Could also be the rendering itself, if complicated, but doesn't look like it is.
However, guessing is generally an unproductive way to analyze performance problems. Use the provided tools. The CPU Sampler instrument could tell you what is really going on.
To fix? First confirm the problem. If it is the file I/O, move it off the main thread (I haven't looked at UIImage's documentation to know if it is thread safe in such a context).
Does anybody know of an open source PDF creation project for the iPhone to generate documents from a template (using Quartz)? Or are there any functions people have written for alignment etc? I've seen libHaru but I understand it is lacking some crucial functionality.
Thanks
You can create a Quartz context with a PDF file as a rendering destination and simply draw into that. For example, the following code will create a PDF file within an NSData object, which you could then attach to an email or save to disk:
NSMutableData *pdfData = [[NSMutableData alloc] init];
CGDataConsumerRef dataConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)pdfData);
const CGRect mediaBox = CGRectMake(0.0f, 0.0f, drawingWidth, drawingHeight);
CGContextRef pdfContext = CGPDFContextCreate(dataConsumer, &mediaBox, NULL);
UIGraphicsPushContext(pdfContext);
CGContextBeginPage(pdfContext, &mediaBox);
// Draw your content here
CGContextEndPage(pdfContext);
CGPDFContextClose(pdfContext);
UIGraphicsPopContext();
CGContextRelease(pdfContext);
CGDataConsumerRelease(dataConsumer);
There are a few things going on here. First, we create an NSMutableData instance and set it to be a data consumer (a destination for the PDF context to write to). Because Core Graphics uses Core Foundation types, and not Cocoa classes, CGDataConsumerCreateWithCFData() requires a CFMutableDataRef argument. We are able to simply cast the NSMutableData class we created as this type, because NSData is a toll-free bridged class. What that means is that it can be used in either Cocoa methods or Core Foundation functions without conversion between types.
After that, we set the page size of the PDF context (in points) and create a PDF context, using the data consumer we set up before. We then make this the active context for drawing by using UIGraphicsPushContext().
In this case, we are only creating a single page in the PDF we're drawing, so we begin the page, draw, then end the page. If you wanted to do multiple pages, you could repeat this for each page.
Note that all of this drawing will be done in the Quartz coordinate space, so if you've set up your drawing routines to display correctly in an iPhone view, it will be flipped here. To counteract this flipping, you can place the following within your drawing code (after UIGraphicsPushContext()):
CGContextTranslateCTM(context, 0.0f, self.frame.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
There 2 ways to generate a pdf in iPhone SDK. The first one is using Core Graphics methods:
CGContextRef CGPDFContextCreateWithURL(CFURLRef url, const CGRect *mediaBox, CFDictionaryRef auxiliaryInfo)
void CGContextBeginPage(CGContextRef c, const CGRect *mediaBox)
void CGContextEndPage(CGContextRef c)
The second way is to use the UIKit methods of creating a pdf context. You can find it here.
There is a good tutorial on generating a pdf programmatically with the iPhone SDK, and you can find it here.
I have been working with Apple's iPhone CoreDateRecipes sample code to learn more about tableviews and core data. I have coded my own test app based off of that sample, and it works well except for one thing. When I choose a photo for the 'recipe', no matter if it is from the camera or the library, when I hit "Done" to leave editing mode, it takes about 15 seconds before returning control to the user. This happens when testing on the device - in simulator, there is still a delay, but it is only 2-4 seconds.
I tested the "edit/done" button without choosing a photo and editing other data, and it saves instantaneously, so I'm pretty sure the image is to blame. Below is the code where it leaves editing mode, and the image processing code - what can I add/change/remove to speed this up? I know these sample code pieces are just proofs of concept, but I can't believe they published an example with such a crappy user experience!
Thanks, as always, for any guidance...let me know if there is any other code you need to see, or you can see the whole sample project here
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[self updatePhotoButton];
nameTextField.enabled = editing;
overviewTextField.enabled = editing;
[self.navigationItem setHidesBackButton:editing animated:YES];
if (!editing) {
NSManagedObjectContext *context = recipe.managedObjectContext;
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Error in RecipeDetailViewController:setEditing -- %#, %#",error, [error userInfo]);
abort();
}
}
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)selectedImage editingInfo:(NSDictionary *)editingInfo {
NSManagedObject *oldImage = recipe.image;
if (oldImage != nil) {
[recipe.managedObjectContext deleteObject:oldImage];
}
NSManagedObject *image = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:recipe.managedObjectContext];
recipe.image = image;
[image setValue:selectedImage forKey:#"image"];
CGSize size = selectedImage.size;
CGFloat ratio = 0;
if (size.width > size.height) {
ratio = 70.0 / size.width;
} else {
ratio = 70.0 / size.height;
}
CGRect rect = CGRectMake(0.0, 0.0, ratio * size.width, ratio * size.height);
UIGraphicsBeginImageContext(rect.size);
[selectedImage drawInRect:rect];
recipe.thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self dismissModalViewControllerAnimated:YES];
}
First, as Gilbert pointed out, example code is not meant for production use and will be slow.
Second, you should not store images in Core Data. The example may show how to do it but it is generally a very bad idea. You should be storing images on disk and then keeping a pointer (file path) to the image in Core Data. There are some exceptions to this rule (very small images) but you should rethink your design.
Lastly, A lot of the slowness you are seeing may not be Core Data related. The image picker code is very slow on its own. I would recommend changing the code to just write to disk and see how slow that is compared to the writing to Core Data. I would be surprised if it was much faster.
Update
You can store small images inside of Core Data and my advice from other posts stands, mores for the desktop than iOS. The reason for this is the cache. On iOS, the cache that Core Data uses is very small. If you store images in the database that are large, you can blow out that cache easily and cause future calls that should be in the cache to hit the disk instead. This is not a hard and fast rule of "don't store binary data" but more of a rule of watch your performance and if you are hitting the disk more than you should, binary data could easily be the cause.
Contacts
With regard to contacts, you can be slower because they are doing things differently than you are and they could easily be using private APIs to access the camera. Because it is Apple, they don't necessarily play by the same rules as us.
I'm not an iPhone developer, but generally, example code does not take into account the user experience. Example code shows examples.
In general, you need to perform expensive (long running) operations in additional threads.
Maybe this blog post will help: Respect the Main Thread
I downloaded the project, built it and ran it in the simulator. I couldn't reproduce your problem. I found that the time it took to save an image was visually instantaneous i.e. no longer than the view transition.
If you see the same issue on the original, unmodified project, then you have something else going on. Make sure you have the latest version of the project which should be the one at your link. I know there is at least one really old one floating around because I hit it recently.
I'm having crashing issues using the Quartz PDF API for iOS. At the moment I am compiling with the SDK 4.0 GM Seed and running on my 3.2 iPad (I have tried using the 3.2 SDK with identical results).
All the code I am using is based on the standard Apple Quartz documentation and from various sources around the internets. So I can't image I'm doing something drastically different or wrong.
The code runs perfectly in the Simulator (all versions, it's a Universal app) and even while using the "Simulate Memory Warning" function. I've used the Leaks tool and there are no leaks that it finds. Build and Analyze also finds nothing. No crash or low memory log is left in my Library.
All this leads me to believe the device is running out of memory. This happens after running through say 50 pdf pages, with about 35% having an image of some sort (some full page some icon). It does not crash on any particular page. The pdf I am loading is about 75 pages and 3.5MB.
I've perused similar issues on this site and around the internets, and have applied some of the advice in the code below. I now release the pdf document reference on every page turn and I no longer retain/release a page reference. I've also simplified the image swapping from using CGImages to just using the UIGraphicsGetImageFromCurrentImageContext function. I've tried various implementations for switching the images, including replacing the pdfImgView completely with a newly allocated temp instance (using [[UIImageView alloc] iniWithImage:UIGraphicsGetImageFromCurrentImageContext()]), using the setter for pdfImgView and releasing the temp. All of the variations pass the Leaks and Analyzer tests, but still exhibit the same crashing behavior.
So, before I move away from PDFs altogether, is there something I should try or something I am missing?
View controller code that is called in interface handlers to swap pages and on first load:
[self drawPage];
// ...animation code...simple CATransition animation...crashes with or without
// scrollView is a UIScrollView that is a subview of self.view
[scrollView.layer addAnimation:transition forKey:nil];
// pdfImgView is a UIImageView that is a subview of scrollView
pdfImgView.image = UIGraphicsGetImageFromCurrentImageContext();
drawPage method used to configure and draw PDF page to the context:
[CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("BME_interior.pdf"), NULL, NULL);
pdfRef = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL); // instance variable, not a property
CFRelease(pdfURL);
CGPDFPageRef page = CGPDFDocumentGetPage(pdfRef, currentPage);
CGRect box = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
// ...setting scale and imageHeight, both floats...
if (UIGraphicsBeginImageContextWithOptions != NULL) {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.view.frame.size.width, imageHeight), NO, 0.0);
} else {
UIGraphicsBeginImageContext(CGSizeMake(self.view.frame.size.width, imageHeight));
}
CGContextRef context = UIGraphicsGetCurrentContext();
NSLog(#"page is %d, context is %d, pdf doc is %d, pdf page is %d", currentPage, context, pdfRef, page); // all prints properly
// ...setting up scrollView for new page, using same instance...
CGContextTranslateCTM(context, (self.view.frame.size.width-(box.size.width*scale))/2.0f, imageHeight);
CGContextScaleCTM(context, scale, -1.0*scale);
CGContextSaveGState(context);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
CGPDFDocumentRelease(pdfRef);
pdfRef = NULL;
Aha! I've fixed the crashes by adding a UIGraphicsEndImageContext(); before beginning a new image context. I don't even get memory warnings now...
Calling
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextSetRenderingIntent(context, kCGRenderingIntentDefault);
before CGContextDrawPDFPage solved a similar problem of mine.
Credits goes to this answer of Johann:
CGContextDrawPDFPage taking up large amounts of memory