2

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 :-)

Vladimir Gritsenko
  • 1,669
  • 11
  • 25

2 Answers2

2

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

Kai Huppmann
  • 10,705
  • 6
  • 47
  • 78
  • 1. Considering it's easy to reproduce on Apple's own code, that's quite unreliable. 2. Apple's PhotoScroller uses one tiled view, to which I add one custom UIView. I don't even draw anything in drawRect - simply having it is enough to cause trouble. – Vladimir Gritsenko Apr 30 '12 at 14:40
  • @Vladimir Gritsenko You are right: You don't have to draw anything ad even though iOS is allocating a lot of memory. I don't get what you mean by "unreliable". drawRect was invented for single views with complicated drawings, but it should not be used with many instances or complex view-subview hierarchies. – Kai Huppmann Apr 30 '12 at 15:05
  • I don't have many instances. In Apple's example, there are two UIView instances - the TilingView and my own custom UIView subclass. This is not an issue with instances, but rather some layer cache magic gone awry. I just don't know why or how to fix it :-( – Vladimir Gritsenko Apr 30 '12 at 15:13
  • @Vladimir Gritsenko All I can say is: I had a similar problem, there's is a lot talk on the internet about drawRect and memory, makes sense to me that iOS can optimize rendering of views when it uses its own drawing (instead or mine or yours), I stopped implementing drawRect on scrolling and zooming user interfaces and I'm happy now. – Kai Huppmann Apr 30 '12 at 15:18
1

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.

Adam
  • 32,900
  • 16
  • 126
  • 153
  • I tried that in the PhotoScroller, and that doesn't seem to help as well. I'm guessing it has to do with some particular setting, but the PhotoScroller setup is pretty simple. :-( – Vladimir Gritsenko Apr 30 '12 at 15:52
  • So, with no subviews of the CATL-view ... you get a crash in PhotoScroller? Sounds to me this is serioues enough to be worth submitting a Technical Support Request to Apple (one of your 2 free ones / year). – Adam May 01 '12 at 11:02
  • In the end it turns out the issue is more boring (I really did have too many full-screen drawRect-implementing UIViews (10-20 overall)), or in the case of PhotoScroller, one very big UIView (2000x2000 or more). Nevertheless, thank you for your prompt response :-) – Vladimir Gritsenko May 01 '12 at 11:10