我创建了一个帮助器类,以便在我的应用程序中轻松加载图像 - 它得到了使用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的旧项目。
有什么想法吗?
答案 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
如果你真的想确保各种字符串和数组变量(即fileName
,paths
,documentsPath
和savePath
)不被放入进入调用者的自动释放池,你可以解决这个问题,但我不确定它是多么重要(至少与放在池中的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;
}
我在这里做了几件事:
和以前一样,我删除了不必要的NSData
逻辑。无需将文件加载到NSData
,然后从中创建UIImage
,然后再放弃NSData
。
如果您为同一个SKU /制造商重复调用此图像,则可以使用NSCache
存储它加载的图像,从而节省大量内存(以及提高性能)。如果您碰巧多次请求相同的图像,它会阻止您创建重复的图像。使用NSCache
解决了这个问题。通过我用图像的文件名键入NSCache
,这是一个方便的键(虽然你也可以使用一些由制造商代码和SKU组成的字符串;这取决于你)。
我利用dispatch_once
来设置两个静态变量:
documentsPath
(如果你这几十次调用它会有一个可观察到的影响,如果你只召唤几百次,这种改善可能是不可观察的)< / p>
cache
(如果您希望缓存在此方法的调用实例之间保持不变,则需要执行类似的操作,使其成为static
,这样才能确保坚持,但通过dispatch_once
)
坦率地说,我倾向于将documentsPath
和/或cache
作为某些单例实例的实例变量,并将这些变量设置为适当的init
方法而不是使用dispatch_once
,但我试图通过修改您与我们分享的方法向您展示如何做到这一点。
真的很小的变化,但我总是使用camelCase(以小写字母开头)表示变量名,所以我将ItemSKU
更改为itemSKU
。
虽然我已经使用了@autoreleasepool
块,但通常不需要这样做,除非您在一个for
循环中多次调用此方法,例如。如果这些是在表视图或集合视图中使用的缩略图,则不需要@autoreleasepool
块。但我保留了这些,以防其中一种非常特殊的情况适用。
就个人而言,我在自包含的代码块周围使用@autoreleasepool
块,而不是返回某些值的代码。但如果你的情况需要它,你可以做类似上面的事情。
如果您为同一图像多次调用此方法,则使用cache
将对内存消耗和性能产生巨大影响。对static
使用dispatch_once
和documentsPath
会产生适度的性能影响,但是如果您正在调用很多,那么它会变得明显并且会对您进行改进可能想考虑一下。
如果你看到内存增加,使用@autoreleasepool
块是有用的,但是当它完成后会回落到合理的水平,但你只想减少那个“高水位线”。如果问题是内存永远不会丢失,那么自动释放池对你没有帮助;问题出在其他地方。
你应该自己玩这个,通过探查器运行它,并检查性能和内存使用情况。就个人而言,我一般会专注于缓存的使用,而不是那么担心@autoreleasepool
,除非你有一些特殊的关于你如何调用这种方法(例如你在一个单独调用它成千上万次) for
循环),但这是需要考虑的事情。对于大多数情况,真正的好处将来自使用缓存,而不是@autorelease
块。