ios - 在iOS中加载放弃内存的图片的辅助方法-如何避免这种情况?

标签 ios memory-leaks profiler

我创建了一个帮助器类,以使在整个应用程序中轻松加载图像变得非常有用:

@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


我正在使用探查器来查看为什么我的应用程序持续崩溃。我正在使用Leaks工具和Heapshots来查看遗弃的内存中正在徘徊的内容-看起来这真使我丧命。

如何解决此方法?这是一个旧项目,已转换为ARC。

有什么想法吗?

最佳答案

您正在自动释放池中创建一个自动释放对象(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;
}


我在这里做两件事:


和以前一样,我删除了不必要的NSData逻辑。无需将文件加载到NSData,然后从中创建UIImage,仅需丢弃NSData
如果您针对同一SKU /制造商重复调用此图像,则使用NSCache存储加载的图像将节省大量内存(并提高了性能)。如果您碰巧多次请求相同的图像,它可以防止您创建重复的图像。使用NSCache解决了该问题。通过用图像的文件名键入NSCache,这是一个方便使用的密钥(尽管您也可以使用由制造商代码和SKU组成的一些字符串;这取决于您)。
我利用dispatch_once来设置两个静态变量:


documentsPath(如果您拨打数万次,这会产生明显的影响,如果仅拨打数百次,则可能无法观察到这种改善)
cache(如果您希望缓存在此方法的调用实例之间持久存在,则需要执行类似的操作,将其设置为static,以确保其持久存在,但是可以通过dispatch_once对其进行一次设置)


坦率地说,我倾向于将documentsPath和/或cache作为某些单例实例的实例变量,并在适当的init方法中设置这些变量,而不是使用dispatch_once,但是我正在尝试向您展示如何通过修改与我们共享的方法来做到这一点。
确实是很小的更改,但是我始终使用camelCase(以小写字母开头)作为变量名,因此我将ItemSKU更改为itemSKU
虽然我使用了您的@autoreleasepool块,但是通常不需要,除非您在单个for循环中多次调用此方法。如果这些是在表视图或集合视图中使用的缩略图,则不需要@autoreleasepool块。但是,如果这些非常特殊的情况之一适用,我会保留在那里。

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


如果对同一张图片多次调用此方法,则cache的使用将产生巨大的影响(在内存消耗和性能方面)。对于static,将dispatch_oncedocumentsPath的使用对性能的影响不大,但是如果您经常这么称呼它,则它会变得引人注目并且是您可能要考虑的改进。

如果看到内存增加,则使用@autoreleasepool块很有用,但是当完成时,它会回落到合理的水平,但您只是想减少该“高水位线”。如果问题是内存从未减少过,那么自动释放池将无济于事。问题出在其他地方。

您应该自己尝试一下,通过探查器运行它,并检查性能和内存使用情况。就我个人而言,我通常将重点放在缓存的使用上,而不用担心@autoreleasepool,除非您对如何调用此方法有一些异议(例如,在单个循环),但这是需要考虑的事情。对于大多数情况,真正的好处将来自缓存的使用,而不是for块。

关于ios - 在iOS中加载放弃内存的图片的辅助方法-如何避免这种情况?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16615556/

相关文章:

ios - firebase pod 安装的版本少于现有的最新版本

ios - 我可以存储 paypal 信息,例如 paypal 的用户 ID 和密码以及用于自动充值功能的信用卡详细信息吗?

java - Process.exec() 中的 JVM 内存泄漏

java - Android camera2 API 内存泄漏

c++ - 基于带有英特尔 Vtune 放大器的挂钟时间的 C++ 程序简介

iphone - 程序收到信号: SIGKILL mean when profiling an app running on device and using the xcode profiler to detect leaks是什么意思

ios - 什么是前置摄像头的deviceUniqueID?

ios - 动态 UILabel 截断文本

java - 如何知道特定功能的内存使用情况?

python - 如何让 Python 分析器工作?