【问题标题】:Reduce Memory Usage in iOS App without leaks减少 iOS 应用程序中的内存使用量而不会泄漏
【发布时间】:2013-07-12 16:36:58
【问题描述】:

我的 iOS 应用内存使用量很高,但没有内存泄漏。我如何减少内存使用量。

使用 Instruments,我发现我的应用在内存警告出现之前最大为 90MB,并且其他内存被释放,然后在剩余的使用中保持在 55-65MB 左右。

我觉得55-65MB太高了吧?

此后,Instruments 没有发现任何泄漏。我可以做些什么来减少这种内存使用量?

我浏览了今年的 WWDC 视频,但在我理解的内容中(这是我的第一个 iOS 应用),它主要涉及处理泄漏。

一些可能有用的信息:

VM:ImageIO_GIF_Data 30.35MB 实时字节 | 115 生活 | 300 瞬态 | 136.12 MB 总字节数

VM:MappedFile 36.04 MB 实时字节 | 16 生活 | 11 瞬态 | 36.09 MB 总字节数

所有其他内容均小于 1MB

我的应用程序从互联网上下载了大约 30 个 GIF 文件,我使用 SDWebImage,我只保存图像的 URL,其余的由 SDWebImage 完成。 :P

提前致谢,

来自 iOS 内存管理初学者


再次感谢您的帮助

【问题讨论】:

  • 如果您对该问题投反对票,请告诉我原因,以便我可以改进问题或不再发布此类问题。谢谢!
  • 你说你用过 Instruments。分配工具说什么是你的主要记忆用户?有关一些介绍,请参阅“恢复您已放弃的内存”:developer.apple.com/library/mac/#documentation/developertools/…
  • 我提供了有关上述前两个统计数据的信息。将很快发布截图。 @jaredsinclair
  • @jaredsinclair 截图在这里!

标签: ios objective-c cocoa-touch automatic-ref-counting instruments


【解决方案1】:

SDWebImage 不会做其余的事情。 您需要尽可能少地处理内存中的图像: 未显示时擦除 UIImageView; 使用可重用对象模式; 当然,当您收到内存警告时,清除不可见(缓存在内存中)的图像, 为此只需使用self.urImage = nil;

所以,好好看看应用程序的内存节省;)

【讨论】:

  • 我确实使用可重复使用的单元格。 GIF 是在表格视图中结构化的,所以我假设当单元格不可见时,它们的图像视图被删除(ARC 处理这个,对吗?)当我收到内存警告时,我将研究清除缓存的方法
  • "缓存被设计为在收到内存警告时自行刷新,因此您不必担心。" github.com/rs/SDWebImage/issues/255
  • SDWebImage 清除未使用的图像,如果您仍然处理它的引用,图像将不会被清除。你如何在表格视图中使用单元格?如果每次都设置 setImageWithUrl 就好了。另一个问题是使用缩放的图像,所以如果 gif 是 1024x768 而你需要 320x240,内存使用量最多 10 倍。使用 stackoverflow.com/questions/185652/… 中的 imageByScalingProportionallyToSize 方法并将其保存到缓存中
  • 我确实使用了 setImageWithUrl,但是如果我使用它,如何将它与 imageByScalingProportionallyToSize 集成,因为它不会已经设置图像,然后我必须使用 imageByScalingProportionallyToSize?感谢您的帮助,我很感激,我只需要了解它,谢谢。 :) @HotJard
  • 实际上我发现如果我将它添加到 setImageWithUrl 源代码中它可以工作,我现在将检查它的性能/内存差异。再次感谢@HotJard
【解决方案2】:

您说您正在使用表格视图。尽管单元格是自动重用的,但这很容易出错并创建太多对象。 1 个常见错误是在 cellForRowAtIndexPath 方法中分配对象(例如 UIImageView),因为这意味着每次重用一个单元格时,都会向其中添加一个新的 UIImageView 并保留旧的 UIImageView。因此,请仔细检查您的 cellForRowAtIndexPath 方法中发生了什么。

【讨论】:

  • 我对所有单元格使用 [cell.anImageView setImageWithURL:someURL] ,但这只是设置图像,而不是分配它。如果单元格不存在if (!cell){//create cell},我创建表格视图单元格,并在我的初始化方法中创建一个图像视图。这是正确的,对吧?感谢您的帮助。 :)
【解决方案3】:

我建议您使用 Instruments 和 Heapshot Analysis。 bbum 在his blog 写了一篇关于它的文章。

这里是一个快速概述:

  1. 在 Instruments 中启动您的应用并选择 Allocations 模板
  2. 在您的应用开始稳定后等待一段时间
  3. 在分配中,按Mark Heap;这是您的基准。
  4. 使用您的应用程序并返回到与 #2 相同的屏幕。再按Mark Heap
  5. 重复一段时间。

如果您看到内存稳定增长,您可以深入到堆中并查看所有已分配的对象。这应该是减少内存占用的良好开端。

【讨论】:

  • 一键下载图片,内存占用相当稳定。我认为只是 GIF 内容太重,所以内存使用率很高,我正试图找到一种缩小文件大小的方法。 @mkalmes
【解决方案4】:

我决定添加完整的代码以节省内存,如果您使用的是 GIF 文件,请修改 UIImage 缩放方法(在这里找到它,一个 Stackoverflow)。正如 SD Image 中所说的 GangstaGraham 存在方法 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

还有 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!
猜你喜欢
  • 1970-01-01
  • 2014-04-19
  • 1970-01-01
  • 2012-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多