2013-05-17 18 views
0

我有我创建可以很容易地在我的应用程序加载图像的辅助类 - 它获得的用于ALOT:在iOS中加载放弃内存的图像的辅助方法 - 我如何避免这种情况?

@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

+2

你确定问题在这里,而不是使用这些图像的任何代码? – rmaddy

+0

不,我可能在将这个项目转换为ARC时做了一些假设 - 正如你所看到的我的堆积增长是荒谬的 – Slee

+0

@rmaddy它看起来你是对的,这是一个开始支持iOS 4的旧项目它有许多代表没有设置为弱点,所以整个观点被抛弃到记忆中 - 作为答案添加,所以我可以给你信用。 – Slee

回答

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(有,如果你打电话的时候的这数以万计的可观察到的影响,改善恐怕不会可观察的,如果你调用这个只有几百倍)

    • cache(如果你想你的缓存将调用的情况下,坚持这种方法,你需要或者做类似的东西,使得它static,这是为了确保它仍然存在,但通过dispatch_once一旦将它设置)

    坦率地说,我倾向于在适当的init方法移动documentsPath和/或cache一些单一实例的实例变量并设置这些变量,而比使用dispatch_once,但我试图告诉你如何通过修改你与我们分享的方法来做到这一点。

  4. 真的很小的变化,但我总是用变量名称来使用camelCase(以小写字母开头),所以我将ItemSKU更改为itemSKU。例如,当我使用@autoreleasepool块时,通常不需要这样做,除非您在单个for循环内调用此方法的次数很多。如果这些是在表格视图或集合视图中使用的缩略图,则不需要@autoreleasepool块。但是我保留在那里以防万一这些特殊情况适用。

    就个人而言,我使用围绕自包含代码块的@autoreleasepool块,而不是返回某个值的代码。但是如果你遇到这种情况,你可以像上面那样做。

使用该cache都会有很大的影响(无论是在内存消耗和性能方面)如果你调用此方法一次以上相同的图像。使用staticdispatch_once代替documentsPath的性能影响不大,但如果您打电话给很多,那么它会变得很明显,并且您可能会考虑优化。

使用@autoreleasepool块可以在内存增加的情况下使用,但在完成后会回落到合理的水平,但您只是想减少“高水位”。如果问题是内存永远不会下降,那么自动释放池不会帮助你;问题在于其他地方。

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

+0

我只是在尝试任何与autorelease池,这个解决方案仍然给我相同数量的堆增长:( – Slee

+0

@Slee它似乎不可能有可能,它有相同数量的堆增长,因为我们已经取出其中一个最大的项目是'NSData',显然你不能摆脱图像(因为这就是你要返回的东西,你大概需要这个)我怀疑rmaddy是对的,问题在于调用你可以分享它在做什么吗?另外,你是否可以多次调用这个缩略图?如果是这样,你应该对这些图像使用'NSCache',这将大大减少内存使用。 – Rob

+0

@Slee I'我真正的问题是如何调用getThumbnailImageIfExists方法,但希望我的扩展答案能让你思考其他想法,顺便说一下,我假定你使用的是ARC,如果不是, ,你需要适当的'release'和'autorelease'语句。 – Rob

相关问题