Press "Enter" to skip to content

UIGraphicsGetCurrentContext() Performance

In an app I’m currently working on I had two choices, either make 135 small thumbnail
images or I auto-generate them by flattening the UIView into a single UIImage then
use that as the background the UIButton. Having never done this, I figured it would
be a great chance to learn. I’m not even going to consider making it a UILabel (title),
UIImageView (icon), UIImageView (shadow), UIButton (actual hidden button) as scrolling through the 135 buttons would be unbearable.

My first attempt was very naive and rather brute force-ish since I was doing this for
each of the 135 buttons. Also note that before a button was made an autorelease pool
was created and then released after the button was made. Here’s a sample of what I’m trying to make (ignore the wood grain).

Thumbnail Sample

[sourcecode language=”objc”]
UIImageView *icon = [[UIImageView alloc] init];
icon setContentMode:UIViewContentModeScaleAspectFit];
icon.clipsToBounds = YES;
[icon setImage:some_new_image];
icon.layer.cornerRadius = 24.0; // originally wanted rounded corners
icon.layer.masksToBounds = YES;
[icon setBackgroundColor:[UIColor whiteColor]];

[icon setFrame:CGRectMake(X_OFFSET+2, 50.f, THUMBNAIL_INNER_WIDTH-4, THUMBNAIL_INNER_HEIGHT-4)];

UIGraphicsBeginImageContext(icon.frame.size);
[icon.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

UIView *button_content = [[UIView alloc] init];
[button_content setFrame:(CGRectMake(5.f, 0.f, 200.f, BOOK_HEIGHT_STEP))];

UIView *shadowview = [[UIView alloc] initWithFrame:CGRectMake(X_OFFSET, 48.f, THUMBNAIL_INNER_WIDTH, THUMBNAIL_INNER_HEIGHT)];

[shadowview setBackgroundColor:[UIColor whiteColor]];

shadowview.layer.shadowOffset = CGSizeMake(0, 5);
shadowview.layer.shadowColor = [UIColor blackColor].CGColor;
shadowview.layer.shadowOpacity = 0.8;
shadowview.layer.cornerRadius = 24.0;
shadowview.layer.masksToBounds = NO;

[button_content addSubview:shadowview];
[button_content addSubview:icon];
[button_content addSubview:title];

[shadowview release];
[icon release];
[title release];

button_content.backgroundColor = [UIColor clearColor];
button_content.autoresizesSubviews = YES;
button_content.userInteractionEnabled = YES;

UIGraphicsBeginImageContext(button_content.frame.size);
[button_content.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

UIImage *viewImage = [ImageHelper imageFromView:button_content];
// Set up the actual button with this image as the background.
// Release any unreleased objects and do some memory clean up.
[/sourcecode]

In Simulator this worked great. The app was super snappy and I was thrilled, first attempt and i worked great. Then it came time to put it on the device and profile it. This is were everything went downhill. First thing I noticed was that each button made was causing a malloc of 240k AND 120k! Suddenly my app was allocing almost 50 mb in all.

The first call UIGraphicsGetImageFromCurrentImageContext() when we flatten the rounded corner UIImageView causes each of the 120k allocs and when we flatten the entire view down we get an additional 240k. Sweet! So removed the rounded corner one was easy to do as it’s really not needed and saves 120k each time through. But that 240k is the real killer and it is mainly because there’s so many views that it’s trying to flatten. In total there are three views in order of the layering: the icon shadow, the icon and the title.

One way to optimize this would be to make the shadow elsewhere (the icons are all the same size), store it off and just reuse it here, but all in all that really didn’t gain me much. The next idea I had was to only flatten the image with the shadow and make that the background to the UIButton. This would get rid of the 240k allocs and leave me with the 120. This worked pretty well, the app still felt fast scrolling through the buttons, but I wasn’t too thrilled with my memory usage still, now at about 13-15mb. So I decided to completely scrap using the UIGraphicsGetImageFromCurrentImageContext() method to flatten my views into images and rather went for the first option, to make 135 buttons by hand.

After making some of the images, I decided it was time for a test. By this time I was down to the UILabel (title), UIImageView (icon), and the UIButton (hidden button). After running Instruments I have to admit, I was a bit shocked. My memory allocation was down to 1.4mb and the footprint of my app overall was < 10mb. I know I wasn’t using UIGraphicsGetImageFromCurrentImageContext() properly, it’s expensive and really meant more for screenshots and the like, not in a massive loop like this and it’s clear why. Overall I’m pretty happy having gone through this process and I already have some ideas on where to use this, but for this use case, it just wasn’t the right tool.

2 Comments

  1. Mazyod Jaleel Mazyod Jaleel September 11, 2012

    Holy… And I read this after using UIGraphicsGetImageFromCurrentImageContext() in pretty much every view of my app.

    I just decided to search for its performance, for fun, and I see this post, a nightmare D:
    Thanks, I appreaciate the heads up

  2. Chris Chris September 11, 2012

    No problem. The way this seems to eat up so much memory is the flattening a bunch of layers. Also, in my test I’m generating 135 buttons each with multiple layers. I’d assume that if you’re only making a few objects this way and caching them it shouldn’t be too bad. But I guess that’s what instruments is for.

Leave a Reply

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