2
votes

We have an out-of-memory crash on the iPad 3 which we traced to the following scenario:

A UIView which uses a CATiledLayer and draws content (say, a PDF) has subviews with their own drawRect methods (which, for example, highlight search results). This makes Core Animation consume tons of memory (100+ MBs in the VM Tracker instrument), and can easily lead to a crash. While this issue exists on all devices, only on the iPad's Retina display does the cache size grow too large.

This can be reproduced with Apple's PhotoScroller example: subclass UIView, uncomment drawRect, and add an instance to the TilingView. The app will crash on iPad 3. Commenting drawRect resolves the memory usage.

Now, we can drop the subviews and do the drawing in the top-most UIView. However, working with subviews is convenient (since we're representing different, independent layers on top of the PDF). Two questions:

  1. What is a good work-around? Preferably one that allows us to continue working with multiple views.
  2. Why is this happening, exactly? I guess the cache mechanism is working overtime, but it would be great to understand the technical details behind it.

Thanks!

EDIT:

I want to elaborate on Kai's answer. The problem was indeed unrelated to CATiledLayer, but to the usage of UIViews that implemented drawRect.

In the case of PhotoScroller, I created a UIView which was of a size of the image - 2000x2000 and more, which creates a huge backing store if drawRect is present.

In the case of our app, the overlay views are full-screen (=~11 MBs on iPad 3) and we have about 5 of them per page. We keep up to three pages in memory while scrolling, and that means more than 150 MBs extra memory. Not fun.

So the solution is to optimize drawRect away, or use less such views. Back to the drawing board it is :-)

2

2 Answers

2
votes

To 2.: Whenever you implement drawRect in a UIView subclass and have lots of instances of that class, your memory usage will grow dramatically. Reason is that a lot of optimization tricks in UIKit view/subview handling (e.g. when zooming or scrolling) don't work with such objects, because the framework doesn't know what you're doing/what you are drawing. So - independent of retina or not - avoid implementing drawRect, especially when having many objects or many layers of subviews.

To 1.: I didn't exactly get what you are trying, but I implemented an PDF-Viewer which is also able to show additional content on top of the PDF. I did it all with normal UIView hierarchies, images etc. and I fear that's the only reliable work around you'll get

1
votes

My experience:

  1. Never add subviews to a UIView that's backed by a CATiledLayer
  2. Never add sublayers to a CATiledLayer

Unfortunately, that seems to be the only practical answer - Apple's implementation goes horribly wrong in many different ways (not just performance - the rendering itself starts to exhibit visual artifact bugs, some of Apple's rendering code goes weird, etc).

In practice, I always do this:

UIView : view +-- UIView w/ CATiledLayer : tiledLayerView +-- UIview : subViewsView

...and safely add views and subviews to "subViewsView". Works fine.