Tuesday, February 22, 2011

Best way to use UIImageView (Memory Optimized)

We use UIImageView a lot in our apps, right? At the most, we use it for a nice view backgrounds or even as button images.

The easiest way to set an image to the UIImageView is to use the following code:

 myImage.image = [UIImage imageNamed:@"myImage.png"];

This works. The code is short and simple and used to be my favourite code to use. But no more. Let me tell you why. For this tutorial, we are going to make use the Instruments to help you understand why this method is not memory optimized.

This tutorial also kinda teach you to check your app's Allocations (of memory) and what you should expect an app to behave in the memory world.

1. Using imageNamed will prepare a cache (depending on the size of image) which will NEVER be flushed out of the memory unless you reach a memory warning of some level, but by then its too late and your app will crash. Even when you set myImage.image = nil, the image is STILL IN MEMORY.

2. If you are using Interface Builder, and setting the image in Image View Attributes, that is also equal to imageNamed method. The image will be cached immediately when the app is ran.

3. To illustrate this, I created 2 UIImageViews in a single View. Created the IBOutlets for them so that we can easily set the image to it by code. I also add a button on the main View, so that we can switch between the 2 sub UIImageViews.

4. To illustrate this issue, 2 projects have to be created. One project uses imageNamed as the image setter, and the other uses imageWithContentsOfFile, which is the better image setter.

5. The imageNamed Project:
a) The project is simple, so I won't be explaining any of the normal codes. In this project, upon loading, we set the first UIImageView *firstImage. Upon tapping the button, we will set firstImage to nil, and then show the second UIImageView *secondImage.

b) Lets take a look at the Instruments tool of XCOde. The one we are interested now is the one called "Allocations". Allocations is a very effective tool to check your code for unreleased objects that is eating the memory when it is not used. The basic principle in the coding is, you must release what you no longer use.

c) To run it, simply Build the Project first. And then go to, Menu Run -> Run with Performance Tool -> Allocations. For the imageNamed Project, the result of the evaluation is as in the video shown below.

d) What happens when we run it is the app will cache the firstImage image first, since we used imageNamed in the viewDidload. After that, when I press the button, we can see that the "Live Bytes" increase another 3MB+ because we are using imageNamed on the second image. Note that we also write a code to set firstImage.image = nil, but the Live Bytes, remained the same. And after that even we keep on toggling the images while setting the other image to nil, it does not clear the memory at all.


6. So now lets look at the better image setter, called imageWithContentsOfFile Project:
a) It is the similar project, but instead of using imageNamed, we make use of the imageWithContentsOfFile setter.

b) A little note on using it, is that you need to append a suffix of "/" at the beginning of the filename otherwise the path is incorrect and the image couldn't be loaded properly. The code is as follows:

 NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/myImage.png"];
myImage.image = [UIImage imageWithContentsOfFile:fullpath];

c)Run it the same way you did with the imageNamed project. The result of the evaluation if as in the video shown below.

(sorry for bad quality of video but its only at the beginning)

d) In this project, we will see the Live Bytes will only contain the memory of 1 image. In this case the photo of the bulls is about 3.3MB in total. And the photo of the kid on a bike is about 4.4MB in total. And as we toggle between the 2 photos, we can see the Live Bytes remain to be at lowest total in comparison to the imageNamed project.

e) While imageContents are superb for memory management, loading images this way is a little bit slow. So if you are just using a small sized UIImageViews in a larger quantity, it is probably better to use imageNamed method.

ImageName Project:

ImageContents Project: