21

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

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
GangstaGraham
  • 8,865
  • 12
  • 42
  • 60
  • 15
    If you downvote the question, please tell me why so that I can either improve the question or not post a question like this again. Thank you ! – GangstaGraham Jul 14 '13 at 21:19
  • You said you used Instruments. What does the Allocation instrument say is your major user of memory? See "Recovering Memory You Have Abandoned" for some intro: http://developer.apple.com/library/mac/#documentation/developertools/Conceptual/InstrumentsUserGuide/MemoryManagementforYouriOSApp/MemoryManagementforYouriOSApp.html – Rob Napier Jul 15 '13 at 01:20
  • Would you mind posting a screenshot of your app running Allocations in Instruments, with "Statistics" selected (which should be the default), and sorted by Live Bytes with the highest at the top? – jaredsinclair Jul 15 '13 at 01:24
  • I gave information about the top two statistics above. Will post a screenshot soon. @jaredsinclair – GangstaGraham Jul 15 '13 at 03:58
  • @jaredsinclair screenshot is here! – GangstaGraham Jul 15 '13 at 04:13

4 Answers4

4

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.

Darren
  • 10,182
  • 20
  • 95
  • 162
  • I use [cell.anImageView setImageWithURL:someURL] for all cells, but this just sets the image, it does not allocate it. In cases where the cell does not exist `if (!cell){//create cell}`, I create the table view cell, and in my initializer method, I create an imageview. This is correct, right? Thanks for the assistance. :) – GangstaGraham Jul 14 '13 at 22:00
1

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.

mkalmes
  • 144
  • 1
  • 10
  • One call the images are downloaded, the memory footprint is pretty stable. I think just the GIF Content is too heavy, so memory usage is high, I am trying to find a way to downsize the filesizes. @mkalmes – GangstaGraham Jul 16 '13 at 02:34
1

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
HotJard
  • 4,598
  • 2
  • 36
  • 36
0

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

danielbeard
  • 9,120
  • 3
  • 44
  • 58
HotJard
  • 4,598
  • 2
  • 36
  • 36
  • I do use reusable cells. The GIFs are structured in a table view, so I assume that when the cells are not visible, their image views are removed (ARC handles this, right?) I will look into ways to clear the cache, when I get a memory warning – GangstaGraham Jul 14 '13 at 21:17
  • "The cache is designed to flush itself when you get a memory warning, so you shouldn't need to worry it." https://github.com/rs/SDWebImage/issues/255 – GangstaGraham Jul 14 '13 at 22:14
  • SDWebImage clear NOT USED images, if you still handling to it references, the images will not cleared. How do u use cells in table view? If every time set setImageWithUrl it's good. Another problem is use scaled images, so if gif is 1024x768 and u need 320x240, memory usage up's to 10 times. Use imageByScalingProportionallyToSize method from http://stackoverflow.com/questions/185652/how-to-scale-a-uiimageview-proportionally and save it to cache – HotJard Jul 15 '13 at 11:15
  • I do use setImageWithUrl, but if I use it how do I integrate it with imageByScalingProportionallyToSize, since won't it already set the image, and then afterwards I have to use imageByScalingProportionallyToSize? Thanks for the help, I appreciate it, I just need to understand it, thanks. :) @HotJard – GangstaGraham Jul 15 '13 at 21:29
  • Actually I figured out that if I add it to the setImageWithUrl source code it works, I will check the performance/memory difference with it now. Thanks once again @HotJard – GangstaGraham Jul 15 '13 at 21:42
  • unfortunately this approach pauses the GIF files (they become static images) @HotJard – GangstaGraham Jul 15 '13 at 21:58
  • Hmm.. i am using SDWebImage's sd_animatedImageByScalingAndCroppingToSize method in the setImageWithURL method - and my average memory usage is now 10 -15 MB which is much better than before, however scrolling is slower because it takes longer to process @HotJard – GangstaGraham Jul 16 '13 at 02:26
  • Oh, interesting method, I don't know about it (may be in 2th version it doesn't exist). If U use animated GIF this is more problem, because it's multiplying memory. This method can scale image EVERY time, so ur aim is TO SAVE animated scaled image, may be using this method to SDImageCache and add category something like setImageWithUrl:scaledToSize where add to urlStr resolution key, then download image(SD Downloader), scale it using sd_animatedImageByScalingAndCroppingToSize, and save to cache with this key, and after times check cache before downloading. – HotJard Jul 16 '13 at 11:58