3

The app I'm working on is relatively simple but it is throwing memory warnings.  I'm trying to figure out whether the app design needs too much memory and should be redisigned and broken up to use less or the app design is fine but the app itself is bloated and incorrectly gobbling up more memory than needed.

The app downloads an XML file from the web that contains a set of questions and then generates a UIScrollView that displays a list of question controls.  Each question control has a UITextView and a UISegmentedControl, UIButton, UITableView, UITextField, or four UITextFields with a UIButton (custom date control).  Here is a screenshot:

http://i.imgur.com/vpa9Z.png

This setup works great for smaller question sets but the app starts throwing memory warnings with larger sets of over 120 questions.  Here is a typical run with the Allocations and VM Tracker Instruments on a larger set:

http://i.imgur.com/gyWOX.png

The Allocations memory spikes on the xml download and model load but the warnings are not thrown until after the Allocations memory has plateaued.  The VM Tracker memory is still increasing when they are thrown though, which makes me think the controls are still being loaded into memory and that the VM Tracker is the better indicator of the memory growth causing the warnings.  The warnings typically happen when the Resident Size gets greater than 125 MB.  I have found one way to lower the Resident size pretty considerably.  The question controls have a custom view to give them rounded edges and a drop shadow.  If I comment out the drawRect code (shown below) from the custom view the Allocations memory stays the same but the Resident Size drops about 30 MB and does not grow above 93 MB.  I can find a lighterwieght background for the questions but I would prefer to keep the rounded edges and drop shadow if I can get its memory footprint down.

- (void)drawRect:(CGRect)rect {
    // get the contect
    CGContextRef context = UIGraphicsGetCurrentContext();

    //for the shadow, save the state then draw the shadow
    CGContextSaveGState(context);
    CGContextSetShadow(context, CGSizeMake(4,-5), 10);

    //now draw the rounded rectangle
    CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);  

    if(_HighlightColor==nil){
        _HighlightColor = [[UIColor whiteColor] retain];
    }
    CGContextSetFillColorWithColor(context, _HighlightColor.CGColor);

    //since I need room in my rect for the shadow, make the rounded rectangle a little smaller than frame
    CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect)-30, CGRectGetHeight(rect)-30);
    CGFloat radius = 5;
    // the rest is pretty much copied from Apples example
    CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
    CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);

    // Start at 1
    CGContextMoveToPoint(context, minx, midy);
    // Add an arc through 2 to 3
    CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
    // Add an arc through 4 to 5
    CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
    // Add an arc through 6 to 7
    CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
    // Add an arc through 8 to 9
    CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
    // Close the path
    CGContextClosePath(context);
    // Fill & stroke the path
    CGContextDrawPath(context, kCGPathFillStroke);

    //for the shadow
    CGContextRestoreGState(context);
}

Instruments and the memory warnings make it seem like the memory is maxed out but these numbers seem high to me.  I wouldn't think 120 of these question controls in a scrollview would be a problem for the iPad to handle but I don't have a frame of reference on how much memory they should use up.  Considering some of the graphic-intensive games that the iPad can run it does not seem like the simple drawRect code above would eat up over 30 MB in the question controls.  Does this memory usage seem high or is this what you would expect from an app with this many simple UI elements?  Is there something in the drawRect that would eat up a lot of memory or any suggestions on how to optimize it?  If it seems like this should max out the iPad's memory, I will create tabs or pages and limit the number of questions I put onto a tab/page so that only a fraction of the controls will be loaded into memory at one time.  However I would rather not break them up if the iPad should be able to handle them all in memory.  Any input is greatly appreciated.

justin
  • 104,054
  • 14
  • 179
  • 226
Wes
  • 103
  • 1
  • 5

2 Answers2

4

Every view that you have allocated will take up significant memory. The way to avoid using tons of memory when you have a lot of views on screen (or off screen in a scrollview) is to have a pool of views that you reuse, only having a few more than are on screen at any time.

Bad news: This caching and swapping is pretty complicated to set up.

Good news: UITableView does it for you!

When you have an arbitrarily large number of UIViews, the best solution is almost always to put them into a table view, and let Apple do the hard work.

cobbal
  • 69,903
  • 20
  • 143
  • 156
  • So you think the number of views I'm talking about could realistically use up this much memory? I have considered doing the pool of views you suggested but thought it would be laggy and overly complicated to continually figure out which views need to be loaded and unloaded. Great idea on the UITableView! That would take care of the second issue. Hopefully the view generation is light enough that it can keep up with scrolling. I'll give it a shot and let you know. Thanks! – Wes Feb 07 '12 at 07:50
  • @Wes Views take up quite a bit of memory; I believe that each one holds on to a buffer of all pixels for compositing. If your view generation turns out to be significantly slow, you can give each type of row its own identifier (with initWithStyle:Identifier: and deque...WithIdentifier:). That way, you just have to generate each type a few times, and the rest of the time you just fill in the data. I've never actually tried nested tableviews, but I don't see why it shouldn't work. – cobbal Feb 07 '12 at 07:54
2

You can use heap shot analysis (using Instruments) to monitor the memory deltas down to allocation backtrace detail -- that should give you a good enough idea of what is growing and why. Those allocations often indicate what you should have also destroyed during the period.

Also be sure there are no leaks in your program.

justin
  • 104,054
  • 14
  • 179
  • 226
  • 1
    Thanks for the suggestion. I had already used that to get the memory down to where it is now and it is a very useful tool. Hopefully somebody who doesn't know about it will see your post and give it a shot. – Wes Feb 07 '12 at 07:55