Memory Management of multiple simple UIViews

147 views Asked by At

Background:

I'm making an app that is grid-based using ARC. Basically there is a 4x4-8x8 grid in the center of the screen (that takes up most of the screen). This grid is constructed using a single UIView that is tinted some color and lines drawn with drawRect: (I'll be posting all of the relevant code below for reference).

Each of the cells is contained inside an NSMutableArray for each row that is contained inside another NSMutableArray of the rows:

  • Array (Rows)
    • Array (Cols)
      • Cell Contents

In each of these cells, I either have an actor object or a placeholder object. The placeholder object is essentially just a blank NSObject while the actor object has 8 primitive properties and 1 object property.

For instance, one of the actors is a source, which essentially recursively draws a plain UIView from the source across the grid until it hits another actor or a wall of the grid.

enter image description here

The blue and red lines show different UIViews as they are currently running. With a grid this small, memory doesn't seem to be an issue often; however, when the full game runs with an 8x8 grid, there can feasibly be 50+ drawn UIViews on the screen in addition to the UIImageViews that function as the sources, movables, etc. as well as the other UILabels and buttons that are not included in the grid. There can easily be over 100 UIViews on the screen at once, which, even on the latest devices with the best hardware, causes some pretty bad lag.

I have a feeling that this has to do with the fact that I am rendering 100+ views to the screen at once.

Question:

Can I incorporate all of these dynamically drawn lines into one view, or is there a better solution entirely?

drawRect:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGColorRef color = [[self backgroundColor] CGColor];

    int numComponents = CGColorGetNumberOfComponents(color);

    CGFloat red = 0, green = 0, blue = 0;

    if (numComponents == 4)
    {
        const CGFloat *components = CGColorGetComponents(color);
        red = components[0];
        green = components[1];
        blue = components[2];
    }

    CGContextSetRGBFillColor(context, red, green, blue, 0.5);

    float top;
    float cell = [self cellWidth];
    float grid = [self gridWidth];

    //Draw
    for(int i = 0; i < [self size]+1; i++)
    {
        top = i*(cell+lineWidth);

        CGContextFillRect(context, CGRectMake(0, top, grid, lineWidth));
        CGContextFillRect(context, CGRectMake(top, 0, lineWidth, grid));
    }
}

addSources:

- (void)addSources:(NSArray*)sources
{
    for(int i = 0; i < [sources count]; i++)
    {
        NSArray* src = [sources objectAtIndex:i];

        int row = [[src objectAtIndex:1] intValue];
        int column = [[src objectAtIndex:0] intValue];
        int direction = [[src objectAtIndex:2] intValue];
        int color = [UIColor colorKeyForString:[src objectAtIndex:3]];

        float width = [self cellWidth]*scaleActors;
        float x = lineWidth + (([self cellWidth]+lineWidth) * (column-1)) + (([self cellWidth]-width)/2.0);
        float y = lineWidth + (([self cellWidth]+lineWidth) * (row-1)) + (([self cellWidth]-width)/2.0);

        ActorView* actor = [[ActorView alloc] initWithFrame:CGRectMake(x, y, width, width)];
        [actor setType:4];
        [actor setDirection:direction];
        [actor setColorKey:color];
        [actor setIsGlowing:YES];
        [actor setPicture];

        if([self isCreatingLevel])
            [actor setCanRotate:YES];

        [self addSubview:actor];

        [[[self rows] objectAtIndex:(row-1)] replaceObjectAtIndex:(column-1) withObject:actor];
    }
}

Edit: Time Profiler Results

By this point, I have roughly 48 drawn views on the screen, (about 70 views total).

enter image description here

1

There are 1 answers

1
Rob On BEST ANSWER

I'd suggest WWDC 2012 video iOS App Performance: Responsiveness as a good primer in using Instruments to track down these sorts of issues. Lots of good techniques and tips in that video.

But I agree that this number of views doesn't seem outlandish (though I might be tempted to render this all in CoreGraphics). I'm not using your same model, but here is a pure Core Graphics rendering of that graphic with a single UIView subclass:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    // configure the gridlines

    CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
    CGContextSetLineWidth(context, 8.0);
    CGContextSetLineCap(context, kCGLineCapSquare);

    // add the horizontal gridlines

    for (NSInteger row = 0; row <= self.rows; row++)
    {
        CGPoint from = [self coordinateForX:0     Y:row];
        CGPoint to   = [self coordinateForX:_cols Y:row];
        CGContextMoveToPoint(context, from.x, from.y);
        CGContextAddLineToPoint(context, to.x, to.y);
    }

    // add the vertical gridlines

    for (NSInteger col = 0; col <= self.cols; col++)
    {
        CGPoint from = [self coordinateForX:col Y:0    ];
        CGPoint to   = [self coordinateForX:col Y:_rows];
        CGContextMoveToPoint(context, from.x, from.y);
        CGContextAddLineToPoint(context, to.x, to.y);
    }

    // stroke the gridlines

    CGContextStrokePath(context);


    // now configure the red/blue line segments

    CGContextSetLineWidth(context, self.bounds.size.width / _cols / 2.0);
    CGContextSetLineCap(context, kCGLineCapRound);

    // iterate through our array of points

    CGPoint lastPoint = [self.points[0] CGPointValue];
    for (NSInteger i = 1; i < [self.points count]; i++)
    {
        // set the color

        if (i % 2)
            CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
        else
            CGContextSetStrokeColorWithColor(context, [[UIColor blueColor] CGColor]);

        CGPoint nextPoint = [self.points[i] CGPointValue];

        // create path

        CGPoint from = [self coordinateForCenterX:lastPoint.x Y:lastPoint.y];
        CGPoint to   = [self coordinateForCenterX:nextPoint.x Y:nextPoint.y];
        CGContextMoveToPoint(context, from.x, from.y);
        CGContextAddLineToPoint(context, to.x, to.y);

        // stroke it

        CGContextStrokePath(context);

        // save the last point

        lastPoint = nextPoint;
    }
}

screen snapshot

Now, maybe you're doing something else that requires more sophisticated treatment, in which case that WWDC video (or, perhaps iOS App Performance: Graphics and Animations) should point you in the right direction.