0

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

@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 的旧项目。

有什么想法吗?

在此处输入图像描述

在此处输入图像描述

4

1 回答 1

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

如果您真的想确保不将各种字符串和数组变量(即 、和fileNamepaths放入调用者的自动释放池中,则可以解决该问题,但我不确定这有多重要(至少与将被放入池中的相比)。documentsPathsavePathNSData


考虑这个替代实现:

+ (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逻辑。无需将文件加载到 a中NSData然后UIImage从中创建 a ,然后丢弃NSData.

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

  3. 我利用自己dispatch_once设置了两个静态变量:

    • (如果你调用它数万次,它会产生明显的documentsPath影响,如果你只调用它几百次,改进可能不会被观察到)

    • cache如果您希望缓存在对该方法的调用实例中保持不变,您需要执行类似的操作,使其static保持不变,但通过设置一次dispatch_once

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

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

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

    就个人而言,我@autoreleasepool在自包含代码块周围使用块,而不是返回某些值的代码。但是,如果您的情况需要,您可以执行上述操作。

cache如果您对同一图像多次调用此方法,则使用将产生巨大的影响(在内存消耗和性能方面)。staticdispatch_oncefor的使用对documentsPath性能有适度的影响,但如果你经常调用它,那么它就会变得引人注目并且你可能需要考虑改进。

如果您看到内存增加,则使用@autoreleasepool块很有用,但稍后会在完成后回落到合理的水平,但您只是想减少那个“高水位线”。如果问题是内存永远不会下降,那么自动释放池将无济于事;问题出在其他地方。

您应该自己尝试一下,通过分析器运行它,并检查性能和内存使用情况。就个人而言,我通常会专注于缓存的使用,而不是那么担心,@autoreleasepool除非您调用此方法的方式有什么特殊之处(例如,您在一个循环中调用了数千次for),但它是需要考虑的事情。对于大多数场景,真正的好处将来自缓存的使用,而不是@autorelease块。

于 2013-05-17T19:06:18.610 回答