View Controller Lifecycle in iOS6

Some of you may have noticed that your view controllers no longer get sent viewWillUnload or viewDidUnload in iOS 6. That’s because your view controllers don’t automatically unload their views anymore.

Your first thought may be, “Ok, well how do I manually unload my views on a low memory warning? This seems like a step backwards.”

Then you go searching for an answer, and you come up with this:

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    if([self isViewLoaded] && ![[self view] window]) {
        [self setView:nil];
    }
}

However, this is both unnecessary and potentially harmful because of some changes to the underlying support for UIViews. Therefore, in practice, you rarely need to unload a view controller’s view during a low memory warning. (And in theory, you will never have to.)

But why? If you’ve read our entire iOS programming book, you’ve gained the knowledge that a UIView is a subclass of UIResponder (so that it can receive events) and has a pointer to its own CALayer instance (so that it can be drawn on the screen).

A CALayer is a container for a bitmap image. When a UIView runs its drawRect: method, it is creating the bitmap image for its layer. The rest of a layer’s instance variables (many of which it borrows from its UIView, like its frame and backgroundColor) indicate how and where that bitmap image is composited onto the screen.

But the biggest part of the layer (in terms of memory usage) is that bitmap. A layer itself is only 48 bytes and a standard UIView is only 96 bytes, regardless of their size on the screen. The memory consumption for a layer’s bitmap, however, grows very large depending on the bounds of the layer. For example, a full screen retina-display iPad view can be up to a whopping 12 megabytes.

The approach taken by iOS 6 is that a low memory warning should only destroy the layer’s bitmap, but keep the CALayer and UIView objects intact. This makes sense given the memory consumption of the view and layer are relatively small compared to the bitmap. Additionally, a bitmap can be redrawn by having the view run drawRect: again, so it is not like we’ve lost anything.

In fact, something is gained by this approach: your controller no longer needs to re-populate view content after a low memory warning. For example, consider a view controller that maintained a couple of text fields. If that view controller’s view were to go off screen and a low memory warning were to occur, the view controller must save the text currently in those text fields and repopulate them in viewDidLoad or viewWillAppear:. This is no longer an issue because the text fields are never destroyed, thus they retain their text for when the text field’s bitmap is drawn again. This simplifies a very error-prone section of UIViewController code.

There are some additional smarts built into this process of dumping the layer’s bitmap image that are really cool. First, let’s make sure we understand allocation and deallocation. When a memory is allocated (an object, a bitmap, whatever), an appropriately sized chunk of memory from the heap is marked as “in use” and a pointer to the beginning of that chunk of memory is returned. In the case of an object allocation, we think of that pointer as “the object” but really, it’s just the address of a chunk of “in use” memory.

While a chunk of memory is allocated (“in use”), there are guards that prevent code from using that memory unless it is accessed by going through the pointer that was returned during allocation. In a sense, that memory is safe from being screwed with unless you intend to screw with it.

Deallocation simply removes those guards and marks that chunk of memory as “not in use”. This means that the next time you allocate an object, you can use some or all of that “not in use” memory to form a new allocation. Upon deallocation, the values stored in that chunk of memory do not change. While it is possible to access that deallocated memory again and it may be the same, chances are that the memory has changed in some way. Therefore, it’s never safe to access deallocated memory through the pointer you were given upon allocation.

Now, on to why this matters for views and their layers. Each layer has a contents property that points to an object that represents the layer’s bitmap. The type of this object is a private, opaque class named CABackingStore, which contains the actual bitmap as well as some metadata (like whether or not the image has an alpha channel and how many bytes per pixel are used). Thus, it is the CABackingStore that needs to be destroyed when memory is running low.

However, the neat trick here is that a low memory warning does not destroy a CABackingStore. Instead, if a view is offscreen during a low memory warning, its layer’s CABackingStore is set to “volatile”. The volatile flag (which is not the same as the volatile keyword in C), in this context, is like deallocation in the sense that it allows the memory of the CABackingStore to be allocated for a different purpose. The difference between marking this memory as volatile and deallocating it is that the volatile memory can actually be reclaimed; it is not lost forever.

Consider why this is such an interesting optimization. If a view (more accurately, its layer) were to destroy its backing store, the next time the view went on screen it would have to recreate that backing store by running drawRect:. drawRect: is an expensive call, so avoiding it is very important. By allowing the backing store to be reclaimed, the layer can avoid having its UIView execute drawRect: again.

Of course, it is possible that some time between when the backing store was marked as volatile and the view that owned it goes back on the screen, the memory for that backing store was reallocated for a different purpose. In that case, the view would have to run its drawRect: method again to produce its bitmap.

6 thoughts on “View Controller Lifecycle in iOS6

  1. Pingback: View Controller Lifecycle in iOS6 « iCorner

  2. Chris

    I draw text to CALayer which I then add as a sublayer of a view;s layer. However, sometimes the layers are just blank – could this be a memory issue and if so, how do I know a layer has been “invalidated?” I have logged the needsDisplay property of each layer, but it is never YES. Any suggestions or tips you may have would be greatly appreciated.

    Reply
    1. joeconway Post author

      No, it is probably not a memory issue; a layer on the screen isn’t going to get invalidated. It is most likely a positioning, sizing or drawing with bad coordinates issue. You won’t see the needsDisplay value set to YES unless you were to ask in the same method you sent the message setNeedsDisplay.

      Set the background colors of your different layers to something distinct, give them a border to see their edges to make sure what is on the screen is actually on the screen as a first recourse.

      Reply
      1. Chris

        Thanks for your reply. Your suggestion to set the background colors helped me to track down the bug – the layers are created on a background thread and then sent to a uiview when ready via notification. However, when a view is dismissed and another is presented before the old view could be deallocated (for some reason the old view hangs around in memory for about 10 seconds before being deallocated), it received the notification as well causing retain cycles. I have “solved” the bug by immediately removing the view as a notification center observer when no longer needed, but it feels like a hack as the real issue is the delayed deallocation of the original view.

        Reply
  3. joeconway Post author

    Most likely, you will be removing/adding them in viewWillAppear: and viewWillDisappear: – see http://stablekernel.com/blog/uiviewcontroller-best-practices-viewwillappear-or-viewdidload/.

    But also, you still have access to dealloc and you should still use dealloc for ensuring that observers are removed. Dealloc still gets called – it simply happens before the object gets destroyed, and you must not call the superclass’ implementation.

    - (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    }

    Reply
  4. Danny

    What’s your opinion about removing observers, both NSNotificationCenter and KVO?
    We used to take care of that in the dealloc, but now that option is gone. I’ve seen many thoughts around the webs, and I’ve landed on adding observers in the viewWillAppear: and removing in the viewWillDisapper:

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>