GangstaGraham
GangstaGraham

Reputation: 9355

Reduce Memory Usage in iOS App without leaks

My iOS app has high memory usage but no memory leaks. How do I reduce the memory usage.

Using Instruments, I discovered that my app maxes out at 90MB, before a memory warning occurs, and other memory is deallocated, and then it stays around 55-65MB for the rest of its usage.

I feel that 55-65MB is too high, right?

Since, Instruments did not catch any leaks. What can I do to reduce this memory usage?

I went through this year's WWDC video, but of the stuff I understood (this is my first iOS app), it mostly covered dealing with leaks.

Some possibly useful information:

VM: ImageIO_GIF_Data 30.35MB Live Bytes | 115 Living | 300 Transient | 136.12 MB Overall Bytes

VM: MappedFile 36.04 MB Live Bytes | 16 Living | 11 Transient | 36.09 MB Overall Bytes

All the other stuff is under 1MB

My app downloads around 30 GIF files from the internet, I use SDWebImage, and I just save the URLs of the images, and SDWebImage does the rest. :P

Thanks in advance,

From An iOS Memory Management First Timer


Here is a screenshot of what Instruments shows me

Thanks once again for you help

Upvotes: 21

Views: 14184

Answers (4)

HotJard
HotJard

Reputation: 4798

I decided to add full code for memory saving, if you are using GIF files, modify UIImage scale method (Found it here, an Stackoverflow). As said GangstaGraham in SD Image exist method sd_animatedImageByScalingAndCroppingToSize

@interface UIImage (Scaling)

-(UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize;
-(UIImage*) croppedImageWithRect: (CGRect) rect;

@end

@implementation UIImage (Scaling)

- (UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize {

    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        if ([[UIScreen mainScreen] scale] == 2.0) {
            targetSize.height *= 2.0f;
            targetSize.width *= 2.0f;
        }
    }

    NSUInteger width = targetSize.width;
    NSUInteger height = targetSize.height;
    UIImage *newImage = [self resizedImageWithMinimumSize: CGSizeMake (width, height)];
    return [newImage croppedImageWithRect: CGRectMake ((newImage.size.width - width) / 2, (newImage.size.height - height) / 2, width, height)];
}

-(CGImageRef)CGImageWithCorrectOrientation
{
    if (self.imageOrientation == UIImageOrientationDown) {
        //retaining because caller expects to own the reference
        CGImageRetain([self CGImage]);
        return [self CGImage];
    }

    UIGraphicsBeginImageContext(self.size);

    CGContextRef context = UIGraphicsGetCurrentContext();

    if (self.imageOrientation == UIImageOrientationRight) {
        CGContextRotateCTM (context, 90 * M_PI/180);
    } else if (self.imageOrientation == UIImageOrientationLeft) {
        CGContextRotateCTM (context, -90 * M_PI/180);
    } else if (self.imageOrientation == UIImageOrientationUp) {
        CGContextRotateCTM (context, 180 * M_PI/180);
    }

    [self drawAtPoint:CGPointMake(0, 0)];

    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    UIGraphicsEndImageContext();

    return cgImage;
}

-(UIImage*)resizedImageWithMinimumSize:(CGSize)size
{
    CGImageRef imgRef = [self CGImageWithCorrectOrientation];
    CGFloat original_width  = CGImageGetWidth(imgRef);
    CGFloat original_height = CGImageGetHeight(imgRef);
    CGFloat width_ratio = size.width / original_width;
    CGFloat height_ratio = size.height / original_height;
    CGFloat scale_ratio = width_ratio > height_ratio ? width_ratio : height_ratio;
    CGImageRelease(imgRef);
    return [self drawImageInBounds: CGRectMake(0, 0, round(original_width * scale_ratio), round(original_height * scale_ratio))];
}

-(UIImage*)drawImageInBounds:(CGRect)bounds
{
    UIGraphicsBeginImageContext(bounds.size);
    [self drawInRect: bounds];
    UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resizedImage;
}

-(UIImage*)croppedImageWithRect:(CGRect)rect
{

    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect drawRect = CGRectMake(-rect.origin.x, -rect.origin.y, self.size.width, self.size.height);
    CGContextClipToRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height));
    [self drawInRect:drawRect];
    UIImage* subImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return subImage;
}

-(UIImage *) resizableImageWithCapInsets2: (UIEdgeInsets) inset
{
    if ([self respondsToSelector:@selector(resizableImageWithCapInsets:resizingMode:)])
    {
        return [self resizableImageWithCapInsets:inset resizingMode:UIImageResizingModeStretch];
    }
    else
    {
        float left = (self.size.width-2)/2;//The middle points rarely vary anyway
        float top = (self.size.height-2)/2;
        return [self stretchableImageWithLeftCapWidth:left topCapHeight:top];
    }
}

@end

And UIImageView:

#import <SDWebImage/SDImageCache.h>

@implementation UIImageView (Scaling)

-(void)setImageWithURL:(NSURL*)url scaleToSize:(BOOL)scale
{
    if(url.absoluteString.length < 10) return;
    if(!scale){
        [self setImageWithURL:url];
        return;
    }
    __block UIImageView* selfimg = self;
    __block NSString* prevKey = SPRINTF(@"%@_%ix%i", url.absoluteString, (int)self.frame.size.width, (int)self.frame.size.height);
    __block UIImage* prevImage = nil;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^ {

        prevImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:prevKey];
        if(prevImage){
            dispatch_async(dispatch_get_main_queue(), ^ {
                [self setImage:prevImage];
            });
        }else{

            [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:url options:SDWebImageDownloaderFILOQueueMode progress:nil completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                if(error){
                    [selfimg setImageWithURL:url scaleToSize:scale];
                }else{
                    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
                    dispatch_async(queue, ^ {
                        prevImage = [image imageByScalingProportionallyToSize:self.frame.size];
                        if(finished)
                            [[SDImageCache sharedImageCache] storeImage:prevImage forKey:prevKey];
                        dispatch_async(dispatch_get_main_queue(), ^ {
                            [self setImage:prevImage];
                        });
                    });
                }
            }];
        }
    });

    return;
}

-(void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder scaleToSize:(BOOL)scale
{
    [self setImage:placeholder];
    [self setImageWithURL:url scaleToSize:scale];
}


@end

Upvotes: 1

mkalmes
mkalmes

Reputation: 144

I would suggest, that you use Instruments and Heapshot Analysis. bbum wrote an article about it at his blog.

Here is a quick overview:

  1. Start your App in Instruments and select the Allocations template
  2. Wait some time after your App start to settle down
  3. In Allocations, press Mark Heap; This is your baseline.
  4. Use your app and return to the same screen as in #2. Press Mark Heap again.
  5. Repeat that for some time.

If you see a steady growth of memory, you can drill down in the heapshots and see all objects allocated. That should give you a good start to reduce your memory footprint.

Upvotes: 1

HotJard
HotJard

Reputation: 4798

SDWebImage doesn't do the rest. You need handle less images in memory as can: erase UIImageView when it's not shown; use reusable objects pattern; and of course clear not visible (cached in memory) images when you've got memory warnings, for this just use self.urImage = nil;

So, good look for app memory saving ;)

Upvotes: 0

Darren
Darren

Reputation: 10398

You say you are using a table view. Although cells are reused automatically, this makes it very easy to make mistakes and create too many objects. 1 common error is allocating objects (eg. UIImageView) in the cellForRowAtIndexPath method, as this means every time a cell is reused a new UIImageView is added to it as well as keeping the old ones. So double check what is going on in your cellForRowAtIndexPath method.

Upvotes: 4

Related Questions