An on-going conversation about stuff I find interesting

Main menu

Post navigation

How to suck down iOS memory without even trying.

If you build custom UIView or UIControl objects, it’s important to remember that all of your views are backed by a CALayer object, which is essentially a bitmap image of the rendered view object. (This is how iOS is able to very quickly composite and animate views and images and the secret behind iOS’s UI responsiveness.)

However, this has some consequences on memory usage.

For example, if you create a view which is 2048×2048 pixels in size (say you are scrolling through an image), and you don’t switch the backing layer to a CATiledLayer object, then for that view iOS will allocate 2048x2048x4 bytes per pixel = 16 megabytes for the CALayer backing store. Given that the memory budget for the backing store on a small device (such as the previous generation iPod Touch) is only about 8 megabytes, your application will die an ignoble death pretty quickly.

Now not all views are the same. If you create a view that is 2048×2048 pixels in size, you set the background color to a flat color (like ‘gray’), but then you don’t respond to the drawRect method to draw your view’s contents, then the backing store in CALayer is very small. (Internally I believe iOS is allocating odd-sized texture maps in the graphics processor–and if the view is a flat color the graphics processor allocates a single pixel texture and stretches it to fit the rectangle. Also if you’re setting the background to one of the predefined textures, I believe it’s not allocating any memory at all, but instead setting up a texture map from a predefined texture and setting the (u,v) coordinates to make the texture fit.)

(An empty view, by the way, can be useful for laying out the contents inside that view; just respond to layoutSubviews, grab the bounding rectangle, and do some rectangle math to lay the contents out.)

So if you need to create a large container view with a border around it, you may be better off allocating two UIViews: one with the background color inset a pixel from the other view which is the border color. Otherwise, the moment you invoke drawRect to draw the frame, on an iPad with a retina display your container view–if it fills the screen–will require 2048 x 1536 x 4 bytes per pixels = 12 megabytes, just to represent a bordered container.

(Footnote: I have a feeling CAGradientLayer is doing something behind the scenes to cut down on memory usage; otherwise why have this class at all? And if that’s the case, in the event where you need to populate a bunch of controls over a background with a subtle gradient effect, you may want to use this as your view’s backing store rather than overriding the drawRect method and filling the entire view with a gradient.)