使用没有imageNamed的xcassets来防止内存问题?

app*_*tal 20 memory-leaks imagenamed ios7 xcasset

根据Apple文档,建议在iOS7应用程序中使用xcassets,并通过imageNamed引用这些图像.

但据我所知,imageNamed和内存总是存在问题.

所以我做了一个简短的测试应用程序 - 使用imageNamed从xcassets目录中引用图像并启动了分析器...结果如预期的那样.一旦分配的内存没有再次释放,即使我从superview中删除了ImageView并将其设置为nil.

我目前正在开发一个带有许多大图像的iPad应用程序,这种奇怪的imageView行为会导致内存警告.

但在我的测试中,我无法通过imageWithContentsOfFile访问xcassets图像.

那么在iOS7上使用大图像的最佳方法是什么?有没有办法以另一种(更高性能)的方式从xcassets目录访问图像?或者我不应该使用xcassets,以便我可以使用imageWithContentsOfFile?

谢谢您的回答!

the*_*guy 15

更新:缓存逐出罚款(至少从iOS 8.3开始).

我决定使用Apple的"新Images.xcassets".事情开始变得糟糕,当我在应用程序中有大约350mb的图像和应用程序不断崩溃(在Retina iPad上;可能是因为加载图像的大小).

我写了一个非常简单的测试应用程序,我在其中加载三种不同类型的图像(观看分析器):

  1. imageNamed: 从资产加载:图像永远不会被释放,应用程序崩溃(对我来说,我可以加载400张图片,但这实际上取决于图像大小)

  2. imageNamed:(传统上包括在项目中):内存使用率很高,偶尔(> 400张图片)我看到一个调用didReceiveMemoryWarning:,但应用程序运行正常.

  3. imageWithContentsOfFile([[NSBundle mainBundle] pathForResource:...):内存使用率非常低(<20mb),因为图像一次只加载一次.

我真的不会责怪imageNamed:所有方法的缓存,因为缓存是一个好主意,如果你不得不一次又一次地显示你的图像,但苹果没有为资产实现它(或没有记录它)有点令人遗憾它没有实现).在我的用例中,我会选择非缓存,imageWithData因为用户不会再看到图像.

由于我的应用程序几乎是最终的,我真的喜欢使用加载机制来自动找到正确的图像,我决定包装用法:

  • 我从project-target-copy-phase中删除了images.xcasset,并将所有图像"再次"添加到项目和复制阶段(只需直接添加Images.xcassets的顶级文件夹,并确保选中"Add"复选框"目标xxx"已选中并且"为任何添加的文件夹创建组"(我没有打扰无用的Contents.json文件).
  • 在第一次构建期间检查新警告,如果多个图像具有相同的名称(并以一致的方式重命名).
  • 对于App Icon和Launch Images,在project-target-general中设置"不使用资产目录"并在那里手动引用它们.
  • 我编写了一个shell脚本来从所有Contents.json文件生成一个json模型(以获取Apples在其资产访问代码中使用它的信息)

脚本:

cd projectFolderWithImageAsset
echo "{\"assets\": [" > a.json
find Images.xcassets/ -name \*.json | while read jsonfile; do
  tmppath=${jsonfile%.imageset/*}
  assetname=${tmppath##*/}
  echo "{\"assetname\":\"${assetname}\",\"content\":" >> a.json
  cat $jsonfile >> a.json; 
  echo '},' >>a.json
done
echo ']}' >>a.json
Run Code Online (Sandbox Code Playgroud)
  • 从json输出中删除最后一个","逗号,因为我没有在这里手动执行此操作.
  • 我使用以下应用程序生成json模型访问代码:https://itunes.apple.com/de/app/json-accelerator/id511324989?mt = 12(目前免费),前缀为IMGA
  • 我已经使用方法调配编写了一个很好的类别,以便不更改正在运行的代码(并希望很快删除我的代码):

(所有设备和后备机制的实现都不完整!!)

#import "UIImage+Extension.h"
#import <objc/objc-runtime.h>
#import "IMGADataModels.h"

@implementation UIImage (UIImage_Extension)


+ (void)load{
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        Method imageNamed = class_getClassMethod(class, @selector(imageNamed:));
        Method imageNamedCustom = class_getClassMethod(class, @selector(imageNamedCustom:));
        method_exchangeImplementations(imageNamed, imageNamedCustom);
    });
}

+ (IMGABaseClass*)model {
    static NSString * const jsonFile = @"a";
    static IMGABaseClass *baseClass = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString *fileFilePath = [[NSBundle mainBundle] pathForResource:jsonFile ofType:@"json"];
        NSData* myData = [NSData dataWithContentsOfFile:fileFilePath];
        __autoreleasing NSError* error = nil;
        id result = [NSJSONSerialization JSONObjectWithData:myData
                                                    options:kNilOptions error:&error];
        if (error != nil) {
            ErrorLog(@"Could not load file %@. The App will be totally broken!!!", jsonFile);
        } else {
            baseClass = [[IMGABaseClass alloc] initWithDictionary:result];
        }
    });
    return baseClass;
}


+ (UIImage *)imageNamedCustom:(NSString *)name{

    NSString *imageFileName = nil;
    IMGAContent *imgContent = nil;
    CGFloat scale = 2;

    for (IMGAAssets *asset in [[self model] assets]) {
        if ([name isEqualToString: [asset assetname]]) {
            imgContent = [asset content];
            break;
        }
    }
    if (!imgContent) {
        ErrorLog(@"No image named %@ found", name);
    }

    if (is4InchScreen) {
        for (IMGAImages *image in [imgContent images]) {
            if ([@"retina4" isEqualToString:[image subtype]]) {
                imageFileName = [image filename];
                break;
            }
        }
    } else {
        if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) {
            for (IMGAImages *image in [imgContent images]) {
                if ([@"iphone" isEqualToString:[image idiom]] && ![@"retina4" isEqualToString:[image subtype]]) {
                    imageFileName = [image filename];
                    break;
                }
            }
        } else {
            if (isRetinaScreen) {
                for (IMGAImages *image in [imgContent images]) {
                    if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
                        imageFileName = [image filename];
                        break;
                    }
                }
            } else {
                for (IMGAImages *image in [imgContent images]) {
                    if ([@"universal" isEqualToString:[image idiom]] && [@"1x" isEqualToString:[image scale]]) {
                        imageFileName = [image filename];
                        if (nil == imageFileName) {
                            // fallback to 2x version for iPad unretina
                            for (IMGAImages *image in [imgContent images]) {
                                if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
                                    imageFileName = [image filename];
                                    break;
                                }
                            }
                        } else {
                            scale = 1;
                            break;
                        }
                    }
                }
            }
        }
    }

    if (!imageFileName) {
        ErrorLog(@"No image file name found for named image %@", name);
    }

    NSString *imageName = [[NSBundle mainBundle] pathForResource:imageFileName ofType:@""];
    NSData *imgData = [NSData dataWithContentsOfFile:imageName];
    if (!imgData) {
        ErrorLog(@"No image file found for named image %@", name);
    }
    UIImage *image = [UIImage imageWithData:imgData scale:scale];
    DebugVerboseLog(@"%@", imageFileName);
    return image;
}

@end
Run Code Online (Sandbox Code Playgroud)

  • 但是,重点不是缓存驱逐是否有效,而是如何首先防止缓存。如果您知道只使用一次图像将其粘贴到界面中,那么将其缓存也将完全浪费内存。苹果明确表示要在这种情况下使用“ init(contentsOfFile :)”,但不适用于资产目录。这仍然是一个未解决的问题。 (2认同)