I've been doing a lot of iPhone UI work with image files that are used in multiple locations in a single view or in several views throughout the application. In some cases, I'm drawing new icons, usually by compositing 2 small images (each less than 4 KB).
I've thought a bit about optimizing the loading of images, but I'm not sure what the best practices would be. I would guess that it would be worthwhile to save any images that are created or altered using CG functions. With images that aren't altered, what is the overhead of loading images from a bundle?
UIImage* image = [UIImage imageNamed:#"myImage.png"]
With the memory constraints of a mobile device in mind, what factors are most important when considering caching images? The size of the image, the total number of images that may be cached, and the number of times a single image is loaded come to mind.
In the latest performance sessions at WWDC (2011), Apple didn't recommend caching images for most cases. They recommend that you only cache images when you know for a fact, after a performance analysis, that you need to cache images ahead of time because you can't afford the time to load them off disk and decode them. In most cases you probably can afford it.
They specifically noted, as #Till does, that +[UIImage imageNamed:] caches images for the lifetime of your process, and so they recommend using a non-caching loading method, such as +[UIImage imageWithContentsOfFile:]
The reason is that memory is a constrained resource on iOS devices, so if you cache your images, you are likely to cause memory pressure on the system, and apps to get jetsammed. And since iOS 5 jetsams apps using more memory first, if you're caching a bunch of UIImages you're going to make it more likely for your app to get jetsammed.
Related
I am just trying to figure out some memory confusion with my App. Do iphone apps have a cache that store images and such? My memory jumps up when I switch screens, but doesn't go back down when I switch back to the previous screen.
The imageNamed method caches the image, but the cache is purged under memory pressure, so if you're confident that your view controller is being released, then you might not worry about it. If it's an image you don't want cached, use imageWithContentsOfFile and it won't cache. According to the imageNamed documentation:
If you have an image file that will only be displayed once and wish to ensure that it does not get added to the system’s cache, you should instead create your image using imageWithContentsOfFile:. This will keep your single-use image out of the system image cache, potentially improving the memory use characteristics of your app.
Yes. This makes sense, especially for apps with lots of images, because you wouldn't want to be fetching those resources from memory every time a user loaded a view. This is true for dynamically-loaded content as well, which is why apps like Flipboard hog a ton of memory :P
If you're worried about it, just make sure to be more aggressive with releasing resources that are rarely used, for example view-specific images. Other things like background images, which may be on every view, should be kept around in cache.
My iphone application have a lot of high resolution images (eg: 2898 × 779 pixels dimension) and the whole project folder is only 17mb in size but if i run the application and when the first view is loaded the real memory and dirty memory showing in the VM Tracker in Instruments is more than 62mb.Can anyone help me to avoid this?Any suggestions will be thankful.
Images once loaded into memory lose (most of?) their compression. So unfortunately, the images may not look big when on disk (bundled in the app) but they can be a lot bigger once loaded into your app.
one 2898x779 image in ram will effectively use 2898x779x4 bytes = ~9mb, compare that with how big your image is on disk and you should see the difference.
so to actually answer your question, either downsize your images (because your devices screen is probably not that big, unless retina ipad or something) or use a CATiledLayer which will only load up parts of the image that are visible on the screen, and not the whole image.
5 tips to reduce memory issues in iOS apps
1. Use virtual memory
iOS doesn’t use swap file but it does support virtual memory. If an app keeps a lot of data in memory for random access you want to organize it as a mapfile rather then loading it to RAM with
malloc()
. An easiest way to do that is to call
NSData initWithContentsOfMappedFile:
2. Avoid stacking autoreleased objects
When you instantiate objects like NSString with no explicit allocation they live until the release of your autorelease pool – typically until your app quits. Extensive usage of such techniques may lead to a lot of garbage in RAM. Use
NSString initWithContentsOfFile:
so you can later release it instead of
NSString stringWithContentsOfFile:
. The same rule applies to
UIImage imageNamed:
– this is not recommended to use for image loading.
3. Handle memory warnings
Unload unnecessary resources when handling memory warning. Even if you can’t unload any of your stuff call
[super didReceiveMemoryWarning]
in all your UIViewControllers. That will by default free some resources like UI controls on non-front views. Failing to handle this event may make iOS decide that your app deserves killing.
4. Consider limited usage of animated view transitions
Animations like flip transition are noticed to cause RAM usage spikes when executed. This feature is very neat and should be used in many cases but it may trigger memory warnings in a heavily loaded multitasking environment. In particular we strongly recommend to avoid animating OpenGL views.
5. Test your memory footprint on device
Use instruments to test. The most useful tools are Allocations, Leaks and Activity Monitor. Testing on simulator is not relevant in most cases since its memory footprint tends to be completely different. Once you test you can figure out how much RAM each part of your app uses, where are the bottlenecks and how you can optimize.
From http://surgeworks.com/
I am building an apparel catalog on the iPad. The app will contain over 2000 jpg product images at 2048 x 1536 # 72ppi, plus 2 sizes of thumbnails for each image. The large size of the primary images is to allow for zooming in on the products at reasonable resolution. The larger thumbnails will be displayed on each page to show alternate colors for each product. The smaller thumbnails are used in a side-scrolling pop-up filmstrip-style page browser.
I am dynamically resizing the larger thumbnails from the full-sized images as each page is displayed (in a paging UIScrollView). The smaller thumbnails are pre-rendered in photoShop to maximize the performance of the side-scrolling page browser.
Aside from the space taken up on the device from so many images, what other issues or concerns are there around the large number of images in an app like this? Memory management is under control because I am paging the large images in and out as needed as the user moves between pages in the main UIScrollView.
You are going to want to take a look at the PhotoScroller sample code. Those are mighty large images, and will cause your app to crash with the amount of memory they consume.
To page between pages, that means you'll have 2 images always loaded. If you can tile the images, I strongly suggest you do, that will further reduce your memory footprint.
Keep in mind that just because you can use say, 80 MB of memory for your application, does not mean you SHOULD use that much. Be a nice neighbour, your application will run along side other applications which themselves, use memory. Try and reduce your footprint however possible.
If you are dealing with the large image memory management properly (by tiling) and your customers are aware of the sizes of the app and/or downloads then there really isn't much other concerns.
The iPad has enough processing power to handle this kind of image processing pretty swiftly, but I wouldn't expect this to run great on an older iPhone.
To prevent crashing, I would quadruple check that your memory management works the way you say it does, and that you definitely don't ship with NSZombieEnabled on.
In my opinion, if you're dealing with that much data you should build a hybrid app, and host all the images on a web server somewhere. An image that size can approach 1 MB: 1MB * 2000 images = 2 gigabytes of storage space on the phone consumed.
So I've seen this question asked before and in fact I asked it last night but I thought I'd give it another go just to see if I could get any other unique views on the problem.
The Problem — I have an app with a large number of uiimageviews (with image downloaded to disk) in a scrollview which of course has two large problems facing it: Memory use, and performance. In my app memory use isn't so much a problem because I am employing techniques such as dequeuing and reusing imageviews and such. But performance is another thing entirely. Right now, as a memory saving procedure, I only store image filepaths in memory because it would be ridiculous to store images in memory. But the problem with this is that reading from the disk takes more time than from memory and slows down scrolling on the scrollview immensely.
So, what kind of techniques do any of you suggest for something like this? I've seen three20 but don't want to use it because I need high customizability in my view and that just won't do. Image files are not large, but just thumbnail size so there is no scaling or excess size. There's got to be an intuitive way to handle this. The built in photos app handles up to thousands of photos perfectly with low memory and slick and smooth scrolling performance.
Fundamentally, the problem is that you're probably doing a bunch of disk I/O on your UI thread, which is basically guaranteed to cause performance problems.
You should consider loading your images on a background thread and updating the image views on the main thread when the images are loaded. Depending on your use case you can get more or less clever about how far you preload in advance, etc, so you can have images ready. (There might be some usable source code or even Apple sample code out there that does something like this, but I don't know of it off the top of my head.)
You may notice that some applications (not sure about the Photos app) have an intermediate stage where they load a very small thumb size image for all images, and scale it up to the render size, which acts as a placeholder until the full size version is loaded-- if the user scrolls past that image before the full size is loaded, the visible effect is nearly the same as if the image was there all along.
Edit Feb 2014: Note that this question dates from iOS 2.0! Image requirements and handling have moved on a lot since then. Retina makes images bigger and loading them slightly more complex. With the built in support for iPad and retina images, you should certainly use ImageNamed in your code.
I see a lot of people saying imageNamed is bad but equal numbers of people saying the performance is good - especially when rendering UITableViews. See this SO question for example or this article on iPhoneDeveloperTips.com
UIImage's imageNamed method used to leak so it was best avoided but has been fixed in recent releases. I'd like to understand the caching algorithm better in order to make a reasoned decision about where I can trust the system to cache my images and where I need to go the extra mile and do it myself. My current basic understanding is that it's a simple NSMutableDictionary of UIImages referenced by filename. It gets bigger and when memory runs out it gets a lot smaller.
For example, does anyone know for sure that the image cache behind imageNamed does not respond to didReceiveMemoryWarning? It seems unlikely that Apple would not do this.
If you have any insight into the caching algorithm, please post it here.
tldr: ImagedNamed is fine. It handles memory well. Use it and stop worrying.
Edit Nov 2012: Note that this question dates from iOS 2.0! Image requirements and handling have moved on a lot since then. Retina makes images bigger and loading them slightly more complex. With the built in support for iPad and retina images, you should certainly use ImageNamed in your code. Now, for posterity's sake:
The sister thread on the Apple Dev Forums received some better traffic. Specifically Rincewind added some authority.
There are issues in iPhone OS 2.x where the imageNamed: cache would not be cleared, even after a memory warning. At the same time +imageNamed: has gotten a lot of use not for the cache, but for the convenience, which has probably magnified the problem more than it should have been.
whilst warning that
On the speed front, there is a general misunderstanding of what is going on. The biggest thing that +imageNamed: does is decode the image data from the source file, which almost always significantly inflates the data size (for example, a screen sized PNG file might consume a few dozen KBs when compressed, but consumes over half a MB decompressed - width * height * 4). By contrast +imageWithContentsOfFile: will decompress that image everytime the image data is needed. As you can imagine, if you only need the image data once, you've won nothing here, except to have a cached version of the image hanging around, and likely for longer than you need it. However, if you do have a large image that you need to redraw often, then there are alternatives, although the one I would recommend primarily is to avoid redrawing that large image :).
With respect to the general behavior of the cache, it does cache based on filename (so two instances of +imageNamed: with the same name should result in references to the same cached data) and the cache will grow dynamically as you request more images via +imageNamed:. On iPhone OS 2.x a bug prevents the cache from being shrunk when a memory warning is received.
and
My understanding is that the +imageNamed: cache should respect memory warnings on iPhone OS 3.0. Test it when you get a chance and report bugs if you find that this is not the case.
So, there you have it. imageNamed: will not smash your windows or murder your children. It's pretty simple but it is an optimisation tool. Sadly it is badly named and there is no equivaluent that is as easy to use - hence people overuse it and get upset when it simply does its job
I added a category to UIImage to fix that:
// header omitted
// Before you waste time editing this, please remember that a semi colon at the end of a method definition is valid and a matter of style.
+ (UIImage*)imageFromMainBundleFile:(NSString*)aFileName; {
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/%#", bundlePath,aFileName]];
}
Rincewind also included some example code to build your own optimised version. I can't see it is worth the maintentace but here it is for completeness.
CGImageRef originalImage = uiImage.CGImage;
CFDataRef imageData = CGDataProviderCopyData(
CGImageGetDataProvider(originalImage));
CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData(imageData);
CFRelease(imageData);
CGImageRef image = CGImageCreate(
CGImageGetWidth(originalImage),
CGImageGetHeight(originalImage),
CGImageGetBitsPerComponent(originalImage),
CGImageGetBitsPerPixel(originalImage),
CGImageGetBytesPerRow(originalImage),
CGImageGetColorSpace(originalImage),
CGImageGetBitmapInfo(originalImage),
imageDataProvider,
CGImageGetDecode(originalImage),
CGImageGetShouldInterpolate(originalImage),
CGImageGetRenderingIntent(originalImage));
CGDataProviderRelease(imageDataProvider);
UIImage *decompressedImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);
The trade off with this code is that the decoded image uses more memory but rendering is faster.
In my experience, the image cache created by imageNamed does not respond to memory warnings. I've had two applications that were as lean as I could get them as far as mem management, but were still inexplicably crashing due to lack of mem. When I stopped using imageNamed to load the images, both applications became dramatically more stable.
I will admit that both applications loaded somewhat large images, but nothing that would be totally out of the ordinary. In the first application, I just skipped caching altogether because it was unlikely a user would come back to the same image twice. In the second, I built a really simple caching class doing just what you mentioned - keeping UIImages in an NSMutableDictionary and then flushing its contents if I received a memory warning. If imageNamed: were to cache like that, then I shouldn't have seen any performance upgrade. All of this was running on 2.2 - I don't know if there's any 3.0 implications on this.
You can find my other question around this issue from my first app here:
StackOverflow question about UIImage cacheing
One other note - InterfaceBuilder uses imageNamed under the covers. Something to keep in mind if you do run into this problem.