Performance problems with [UIImage imageNamed] in iOS7/iOS8 - objective-c

I have an app that I'm in the process of updating to iOS8 from iOS6. After making a few small changes here and there to get things to compile, I ran the app on my iPad mini and found that the performance had dropped off significantly. In a couple places things that used to have no noticeable delay now frequently take 2-3 seconds to complete. Thinking that perhaps something was wrong with my iPad, I removed my current build and re-downloaded the app from the AppStore. Performance went back to the previous/good state.
After profiling the code, one of the things that sticks out is a significant amount of time in [UIImage imageNamed]. My app makes many calls to this because it has an indoor and an outdoor mode which requires most of the images on a given view to be loaded dynamically. Has something happened to this call in the latest SDKs?
When I profiled the app, here is the method that is eating up >70% of the time
+(UIImage*)imageNamed:(NSString*)rootName extension:(NSString*)extension
{
NSString* typeName = _isIndoor ? #"inside" : #"outside";
NSString* fullName = [NSString stringWithFormat:#"%#-%#.%#", rootName, typeName, extension];
UIImage* result = [UIImage imageNamed:fullName];
if (result == nil) {
NSLog(#"Missing image [name: %#]", fullName);
}
return result;
}
Some other notes:
The original app works fine on my 1st generation iPad Mini running iOS8
The app does not currently use ARC, but I did test switching to ARC w/ no improvement (not surprising)
The app does not use Asset Catalogs, but I tried converting to use Asset Catalogs w/ no improvement (also not surprising)
I did some googling, but wasn't able to find anyone else having the same problem

I assume the [UIImage imageNamed: ] is what is taking up the most time. Considering things were working great before we can make a guess that the "searching" part is what is going awry. Where are the resources stored? Main Bundle or elsewhere?
Also imageNamed: should cache the image, making subsequent calls fast. Is that the case? Or are subsequent calls for the same resource slow as well?
try using the init method on NSImage that takes the inBundle parameter, and pass the correct bundle into that. This way, if the search for the file is the problem, this should be faster.

Related

Best image cache for loading numerous images in UITableView for IOS?

Could any one please tell me the best image cache available for loading numerous images in UITableviewcell?
Currently i am using SDWebimageCache but it seems it is creating problems in IOS9 particularly in iPad where the table scrolling makes the app to crash.
Help me with SDWebimagecache alternative.
The class which i used from SDWebImageCache is:
[cell.imgMain sd_setImageWithURL:imageLoadUrl
placeholderImage:[UIImage imageNamed:#"*placeholder image*"]
options:SDWebImageRefreshCached];
The class which i used from UIIMageView + AFNetworking is:
[cell.imgMain setImageWithURLRequest:[NSURLRequest requestWithURL:imageUrl]
placeholderImage:[UIImage imageNamed:#"wachizLogoIcon.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
cell.imgMain.image=image;
}
failure:nil];
But still the application crashes and shows the following screen:
APPLICATION CRASHES IN IOS 9 and sometimes for IOS 8.4
Please help me from this situation. Lots of pressure ...
AFNetworking has a great UIImage category that does this well and works on iOS9. I too had issues with SDWebimageCache on iOS9.
Take a look at AsyncImageView.
AsyncImageView includes both a simple category on UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI, and a UIImageView subclass for more advanced features. AsyncImageView works with URLs so it can be used with either local or remote files.
Loaded/downloaded images are cached in memory and are automatically cleaned up in the event of a memory warning. The AsyncImageView operates independently of the UIImage cache, but by default any images located in the root of the application bundle will be stored in the UIImage cache instead, avoiding any duplication of cached images.
The library can also be used to load and cache images independently of a UIImageView as it provides direct access to the underlying loading and caching classes
You can use JMImageCache For lazy loading of image in UITableView

Memory warning and crash (ARC) - how to identify why it's happening?

I've started to use the ARC recently and since then I blame it for every single memory problem. :) Perhaps, you could help me better understand what I'm doing wrong.
My current project is about CoreGraphics a lot - charts drawing, views filled with thumbnails and so on. I believe there would be no problem while using manual memory management, except maybe a few zombies... But as of now, application simply crashes every time I try to either create a lot of thumbnails or redraw a bit more complicated chart.
While profiling with Instruments I can see an awfully high value in resident memory as well as in the dirty one. Heap analysis shows rather alarming irregular grow...
When drawing just a few thumbnails, resident memory grows for about 200 MB. When everything is drawn, memory drops back on almost the same value as before drawing. However, with a lot of thumbnails, a value in resident memory is higher than 400 MB and that obviously crashes the app. I've tried to limit number of thumbnails drawn at the same time (NSOperationQueue and its maxConcurrentOperationCount), but as releasing so much memory seems to take a bit more time, it didn't really solve the issue.
Right now my app basically doesn't work as the real data works with a lot of complicated charts = lot of thumbnails.
Every thumbnail is created with this code I got from around here: (category on UIImage)
+ (void)beginImageContextWithSize:(CGSize)size
{
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
if ([[UIScreen mainScreen] scale] == 2.0) {
UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
} else {
UIGraphicsBeginImageContext(size);
}
} else {
UIGraphicsBeginImageContext(size);
}
}
+ (void)endImageContext
{
UIGraphicsEndImageContext();
}
+ (UIImage*)imageFromView:(UIView*)view
{
[self beginImageContextWithSize:[view bounds].size];
BOOL hidden = [view isHidden];
[view setHidden:NO];
[[view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
[self endImageContext];
[view setHidden:hidden];
return image;
}
+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize
{
UIImage *image = [self imageFromView:view];
if ([view bounds].size.width != newSize.width ||
[view bounds].size.height != newSize.height) {
image = [self imageWithImage:image scaledToSize:newSize];
}
return image;
}
+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
[self beginImageContextWithSize:newSize];
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
[self endImageContext];
return newImage;
}
Is there some other way which wouldn't eat so much memory or is something really wrong with the code when using ARC?
The other place where memory warning + crash is happening is when there is too much redrawing of any view. It doesn't need to be quick, just many times. Memory stacks up until it crashes and I'm not able to find anything really responsible for it. (I can see a growing resident/dirty memory in VM Tracker and a heap growth in Allocations instrument)
My question basically is: how to find why it is even happening? My understanding is when there is no owner for given object, it's released ASAP. My inspection of code suggests a lot of objects are not released at all even though I don't see any reason for it to happen. I don't know about any retain cycles...
I've read through the Transitioning to ARC Release Notes, bbum's article about heap analysis and probably a dozen of others. Differs somehow heap analysis with and without ARC? I can't seem to do anything useful with its output.
Thank you for any ideas.
UPDATE: (to not force everybody read all the comments and to hold my promise)
By carefully getting through my code and adding #autoreleasepool, where it had any sense, memory consumption got lowered. The biggest problem was calling UIGraphicsBeginImageContext from background thread. After fixing it (see #Tammo Freese's answer for details) deallocation occurred soon enough to not crash an app.
My second crash (caused by many redrawing of the same chart), was completely solved by adding CGContextFlush(context) at the end of my drawing method. Shame on me.
A small warning for anyone trying to do something similar: use OpenGL. CoreGraphics is not quick enough for animating big drawings, especially not on an iPad 3. (first one with retina)
To answer your question: Identifying problems with memory warnings and crashes with ARC basically works like before with manual retain-release (MRR). ARC uses retain, release and autorelease just like MRR, it only inserts the calls for you, and has some optimizations in place that should even lower the memory consumption in some cases.
Regarding your problem:
In the screenshot of Instruments you posted, there are allocation spikes visible. In most cases I encountered so far, these spikes were caused by autoreleased objects hanging around too long.
You mentioned that you use NSOperationQueue. If you override -[NSOperationQueue main], make sure that you wrap the whole content of the method in #autoreleasepool { ... }. An autorelease pool may already be in place, but it is not guaranteed (and even if there is one, it may be around for longer than you think).
If 1. has not helped and you have a loop that processes the images, wrap the inner part of the loop in #autoreleasepool { ... } so that temporary objects are cleaned up immediately.
You mentioned that you use NSOperationQueue. Since iOS 4, drawing to a graphics context in UIKit is thread-safe, but if the documentation is right, UIGraphicsBeginImageContext should still only be called on the main thread! Update: The docs now state that since iOS 4, the function can be called from any thread, to the following is actually unnecessary! To be on the safe side, create the context with CGBitmapContextCreate and retrieve the image with CGBitmapContextCreateImage. Something along these lines:
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
// draw to the context here
CGImageRef newCGImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *result = [UIImage imageWithCGImage:newCGImage scale:scale orientation: UIImageOrientationUp];
CGImageRelease(newCGImage);
return result;
So, nothing you are doing relative to memory management (there is none!) looks improper. However, you mention using NSOperationQueue. Those UIGraphics... calls are marked as not thread safe, but others have stated they are as of iOS 4 (I cannot find a definitive answer to this, but recall that this is true.
In any case, you should not be calling these class methods from multiple threads. You could create a serial dispatch queue and feed all the work through that to insure single threaded usage.
What's missing here of course is what you do with the images after using them. Its possible you are retaining them in some way that is not obvious. Here are some tricks:
in any of your classes that use lots of images, add a dealloc() method that just logs its name and some identifier.
you can try to add a dealloc to UIImage to do the same.
try to drive your app using the simplest possible setup - fewest images etc - so you can verify that in fact that the images and their owners are getting dealloc'd.
when you want to make sure something is released, set the ivar or property to nil
I converted a 100 file project to ARC last summer and it worked perfectly out of the box. I have converted several open source projects to ARC as well with only one problem when I improperly used bridging. The technology is rock solid.
This is not an answer to your question but I was trying to solve similar problems long before ARC was introduced.
Recently I was working on an application that was caching images in memory and releasing them all after receiving memory warning. This worked fine as long as I was using the application at a normal speed (no crazy tapping). But when I started to generate a lot of events and many images started to load, the application did not manage to get the memory warning and it was crashing.
I once wrote a test application that was creating many autoreleased objects after tapping a button. I was able to tap faster (and create objects) than the OS managed to release the memory. The memory was slowly increasing so after a significant time or simply using bigger objects I would surely crash the application and cause device to reboot (looks really effective ;)). I checked that using Instruments which unfortunately affected the test and made everything slower but I suppose this is true also when not using Instruments.
On the other occasion I was working on a bigger project that is quite complex and has a lot of UI created from code. It also has a lot of string processing and nobody cared to use release - there were few thousands of autorelease calls when I checked last time. So after 5 minutes of slightly extensive usage of this application, it was crashing and rebooting the device.
If I'm correct then the OS/logic that is responsible for actually deallocating memory is not fast enough or has not high enough priority to save an application from crashing when a lot of memory operations are performed. I never confirmed these suspicions and I don't know how to solve this problem other than simply reducing allocated memory.

page-based with storyboard dont dealloc

I am developing a complete application using the template "page-based storyboard".
But whenever I turn page, I see thru the instruments that the amount of memory allocated only increase and never decrease, so until a crash occurs.
Trying in iPad device crash too.
To simplify and try to find the problem, I created a test app using the same template and opting for ARC, only loading the image of the pages that I use and no change anything in the original apple code, even in this simple application the problem occurs.
I think the problem is because all page are stay allocated like this issue
PageViewController: How to release ViewControllers added to it?
but I m using storyboard, so, where is:
PageView *pView = [[PageView alloc] init];
I have:
MWViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:#"MWDataViewController"]
I try to put autorelease but no effect.
The problem that I was having is that I was using background image in all pages and the imageNamed: method caches images which makes my memory footprint grow. I used the UIImage initWithContentsOfFile: method and my foot print stayed mostly flat.

Problems creating lots of thumbnails using AVAssetImageGenerator with ARC

I've looked through every list I can find for ideas on this but have been stuck for a couple of days now so here's hoping...
I'm trying to create a lot of video thumbnails (100 or so) at once using AVAssetImageGenerator.
While testing I'm always using the same movie files, but the process (seemingly) randomly succeeds or fails, plus I now get all the movies loaded into physical memory at once which I don't want. I'm obviously doing something wrong but cannot figure out what - and under ARC can't manually manage the memory.
Background;
It's an AVFoundation video player using ARC (Xcode 4.2.1, OSX 10.7.2, MBP 2.4GHz i5, 4GB RAM).
It has a playlist style interface allowing you to drag folders of movie files to it. It recurses through the paths it's given and plucks out the valid movies and adds them to the list.
Movies are represented by a Movie class which has an AVAsset member variable (movieAsset) initialised with the file path. So far so good - everything works, movies can be played, only the movie being played is in physical memory.
I've added a -createThumbnail method to the Movie class which is called in the Movie's init method (code snippet below).
With the addition of this code I'm getting a few behaviours I can't eradicate - none of which occur if I don't call the -createThumbnail code below. Any ideas where I'm going wrong?
Every movie added to the playlist is now being loaded into physical memory immediately - so the apps memory footprint has gone way up (without thumbnail code = 40MB for 100 movies, with thumbnails (NSImages at 32x18 pixels) 750MB for the same 100 movies).
Looking at Activity Monitor->Open Files and Ports, all the movie files are listed even after thumbnail creation has finished. This didn't occur before - only the movie being played was listed.
Thumbnail creation completely locks up my machine until it's complete - even though I'm calling AVAssetImageGenerator within an asynchronous block - (CPU usage never gets above 35%). Could this be a disk access problem trying to read 100 movies at once?
Thumbnail creation is very erratic. Sometimes all thumbnails are created, but often a random 30-70% are not. Maybe also a disk access problem?
I'm very new to OOP and Obj-C so have probably made a newbies mistake - but I just can't track it down...
Also worth noting that the "Error creating thumbnail" and "Error finding duration" NSLogs are never called...
-(void)createThumbnail{
NSArray *keys = [NSArray arrayWithObject:#"duration"];
[movieAsset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
NSError *error = nil;
AVKeyValueStatus valueStatus = [movieAsset statusOfValueForKey:#"duration" error:&error];
switch (valueStatus) {
case AVKeyValueStatusLoaded:
if ([movieAsset tracksWithMediaCharacteristic:AVMediaTypeVideo]) {
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:movieAsset];
Float64 movieDuration = CMTimeGetSeconds([movieAsset duration]);
CMTime middleFrame = CMTimeMakeWithSeconds(movieDuration/2.0, 600);
CGImageRef imageForThumbnail = [imageGenerator copyCGImageAtTime:middleFrame actualTime:NULL error:NULL];
if (imageForThumbnail != NULL) {
NSSize sizeOption = NSMakeSize(32, 18);
self.thumbnail = [[NSImage alloc] initWithCGImage:imageForThumbnail size:sizeOption];
NSLog(#"Thumbnail created for %#", [self filenameString]);
}
else{
NSLog(#"-----Error creating thumbnail for %#", [self filenameString]);
}
CGImageRelease(imageForThumbnail);
}
break;
case AVKeyValueStatusFailed:
NSLog(#"Error finding duration");
break;
case AVKeyValueStatusCancelled:
NSLog(#"Cancelled finding duration");
break;
}
}];
}
(Note: I've been using the same few folders of movie files to develop the app for the last month or so. They're all local valid files that play successfully in the app. Some of these folders dropped contain over a hundred movie files nested within various subfolders).
Many thanks if anyone can help.
Chas.
I had some weird issues when using AVAssetImageGenerator in this way as well (on iOS at least). It seems to me at least to be somewhat broken when used in combination with the blocks/GCD API. Rather than loading everything asynchronously, try making a single queue/loop that operates in a single background thread, and walk through the movies that way. This should also help to keep your memory usage down.

How to speed up saving a UIImagePickerController image from the camera to the filesystem via UIImagePNGRepresentation()?

I'm making an applications that let users take a photo and show them both in thumbnail and photo viewer.
I have NSManagedObject class called photo and photo has a method that takes UIImage and converts it to PNG using UIImagePNGRepresentation() and saves it to filesystem.
After this operation, resize the image to thumbnail size and save it.
The problem here is UIImagePNGRepresentation() and conversion of image size seems to be really slow and I don't know if this is a right way to do it.
Tell me if anyone know the best way to accomplish what I want to do.
Thank you in advance.
Depending on the image resolution, UIImagePNGRepresentation can indeed be quite slow, as can any writing to the file system.
You should always execute these types of operations in an asynchronous queue. Even if the performance seems good enough for your application when testing, you should still do it an asynch queue -- you never know what other processes the device might have going on which might slow the save down once your app is in the hands of users.
Newer versions of iOS make saving asynchronously really, really easy using Grand Central Dispatch (GCD). The steps are:
Create an NSBlockOperation which saves the image
In the block operation's completion block, read the image from disk & display it. The only caveat here is that you must use the main queue to display the image: all UI operations must occur on the main thread.
Add the block operation to an operation queue and watch it go!
That's it. And here's the code:
// Create a block operation with our saves
NSBlockOperation* saveOp = [NSBlockOperation blockOperationWithBlock: ^{
[UIImagePNGRepresentation(image) writeToFile:file atomically:YES];
[UIImagePNGRepresentation(thumbImage) writeToFile:thumbfile atomically:YES];
}];
// Use the completion block to update our UI from the main queue
[saveOp setCompletionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImage *image = [UIImage imageWithContentsOfFile:thumbfile];
// TODO: Assign image to imageview
}];
}];
// Kick off the operation, sit back, and relax. Go answer some stackoverflow
// questions or something.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:saveOp];
Once you are comfortable with this code pattern, you will find yourself using it a lot. It's incredibly useful when generating large datasets, long operations on load, etc. Essentially, any operation that makes your UI laggy in the least is a good candidate for this code. Just remember, you can't do anything to the UI while you aren't in the main queue and everything else is cake.
Yes, it does take time on iPhone 4, where the image size is around 6 MB. The solution is to execute UIImagePNGRepresentation() in a background thread, using performSelectorInBackground:withObject:, so that your UI thread does not freeze.
It will probably be much faster to do the resizing before converting to PNG.
Try UIImageJPEGRepresentation with a medium compression quality. If the bottleneck is IO then this may prove faster as the filesize will generally be smaller than a png.
Use Instruments to check whether UIImagePNGRepresentation is the slow part or whether it is writing the data out to the filesystem which is slow.