在iOS中加载图像放弃内存的助手方法 - 我该如何避免这种情况?

时间:2013-05-17 18:21:41

标签: ios memory-leaks profiler

我创建了一个帮助器类,以便在我的应用程序中轻松加载图像 - 它得到了使用ALOT:

@implementation Helpers

+(UIImage *) getThumbnailImageIfExists:(NSString *)ItemSKU withManufacturer: (NSNumber *) aManufacturerID { 

    @autoreleasepool {

    NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:ItemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]];
    NSData *imageData = [NSData dataWithContentsOfFile:savePath];

    if  (imageData==nil) 
    {
         return nil;
    }

    return [UIImage imageWithData:imageData];

    }
}

@end

我正在使用Profiler来查看为什么我的应用程序不断崩溃。我正在使用Leaks工具和Heapshots来查看遗弃在内存中的东西 - 看起来这样会让我感到害怕。

如何修复此方法?这是一个转换为ARC的旧项目。

有什么想法吗?

enter image description here

enter image description here

1 个答案:

答案 0 :(得分:1)

您正在自动释放池中创建一个自动释放的对象(imageWithData),然后返回该对象,然后立即耗尽您的池。最简单的解决方法是删除该自动释放池。为什么要有那个游泳池?只是为了立即排空NSData?但是你完全不需要NSData,因为你可以直接检索图像:

@implementation Helpers

+ (UIImage *) getThumbnailImageIfExists:(NSString *)ItemSKU withManufacturer: (NSNumber *) aManufacturerID { 

    NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:ItemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]];

    return [UIImage imageWithContentsOfFile:savePath];
}

@end

如果你真的想确保各种字符串和数组变量(即fileNamepathsdocumentsPathsavePath)不被放入进入调用者的自动释放池,你可以解决这个问题,但我不确定它是多么重要(至少与放在池中的NSData相比)。


考虑这个替代实现:

+ (UIImage *)getThumbnailImageIfExists:(NSString *)itemSKU withManufacturer:(NSNumber *)aManufacturerID
{
    UIImage *image;
    static NSString *documentsPath;
    static NSCache *cache;

    // create docsPath and cache once and only once

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        documentsPath = searchPaths[0];
        cache = [[NSCache alloc] init];
        cache.countLimit = 100;
    });

    // now do your image retrieval

    @autoreleasepool {
        NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:itemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"];
        NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]];

        image = [cache objectForKey:savePath];
        if (!image)
        {
            image = [[UIImage alloc] initWithContentsOfFile:savePath]; // note, not an autoreleased object
            [cache setObject:image forKey:savePath];
        }
    }

    return image;
}

我在这里做了几件事:

  1. 和以前一样,我删除了不必要的NSData逻辑。无需将文件加载到NSData,然后从中创建UIImage,然后再放弃NSData

  2. 如果您为同一个SKU /制造商重复调用此图像,则可以使用NSCache存储它加载的图像,从而节省大量内存(以及提高性能)。如果您碰巧多次请求相同的图像,它会阻止您创建重复的图像。使用NSCache解决了这个问题。通过我用图像的文件名键入NSCache,这是一个方便的键(虽然你也可以使用一些由制造商代码和SKU组成的字符串;这取决于你)。

  3. 我利用dispatch_once来设置两个静态变量:

    • documentsPath(如果你这几十次调用它会有一个可观察到的影响,如果你只召唤几百次,这种改善可能是不可观察的)< / p>

    • cache(如果您希望缓存在此方法的调用实例之间保持不变,则需要执行类似的操作,使其成为static,这样才能确保坚持,但通过dispatch_once

    • 设置一次

    坦率地说,我倾向于将documentsPath和/或cache作为某些单例实例的实例变量,并将这些变量设置为适当的init方法而不是使用dispatch_once,但我试图通过修改您与我们分享的方法向您展示如何做到这一点。

  4. 真的很小的变化,但我总是使用camelCase(以小写字母开头)表示变量名,所以我将ItemSKU更改为itemSKU

  5. 虽然我已经使用了@autoreleasepool块,但通常不需要这样做,除非您在一个for循环中多次调用此方法,例如。如果这些是在表视图或集合视图中使用的缩略图,则不需要@autoreleasepool块。但我保留了这些,以防其中一种非常特殊的情况适用。

    就个人而言,我在自包含的代码块周围使用@autoreleasepool块,而不是返回某些值的代码。但如果你的情况需要它,你可以做类似上面的事情。

  6. 如果您为同一图像多次调用此方法,则使用cache将对内存消耗和性能产生巨大影响。对static使用dispatch_oncedocumentsPath会产生适度的性能影响,但是如果您正在调用很多,那么它会变得明显并且会对您进行改进可能想考虑一下。

    如果你看到内存增加,使用@autoreleasepool块是有用的,但是当它完成后会回落到合理的水平,但你只想减少那个“高水位线”。如果问题是内存永远不会丢失,那么自动释放池对你没有帮助;问题出在其他地方。

    你应该自己玩这个,通过探查器运行它,并检查性能和内存使用情况。就个人而言,我一般会专注于缓存的使用,而不是那么担心@autoreleasepool,除非你有一些特殊的关于你如何调用这种方法(例如你在一个单独调用它成千上万次) for循环),但这是需要考虑的事情。对于大多数情况,真正的好处将来自使用缓存,而不是@autorelease块。