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

时间:2014-04-04 07:27:59

标签: ios7 memory-leaks imagenamed xcasset

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

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

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

我目前正在使用包含许多大图像的iPad应用程序,这种奇怪的imageView行为会导致内存警告。

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

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

感谢您的回答!

1 个答案:

答案 0 :(得分:15)

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

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

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

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

  2. imageNamed:(传统上包含在项目中):内存使用量很高,偶尔(> 400张图片)我看到didReceiveMemoryWarning:的来电,但应用是运行正常。

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

  4. 我真的不会责怪imageNamed:方法对所有内容的缓存,因为如果你不得不一次又一次地展示你的图像,缓存是一个好主意,但苹果公司没有实现它是一件令人伤心的事情。资产(或没有记录它没有实现)。在我的用例中,我将使用非缓存imageWithData,因为用户再也看不到图像了。

    由于我的应用程序几乎是最终版本而且我非常喜欢使用加载机制来自动查找正确的图像,所以我决定将其用于包装:

    • 我从project-target-copy-phase中删除了images.xcasset,并再次添加了所有图像&#34;&#34;到项目和复制阶段(只需直接添加Images.xcassets的顶级文件夹,并确保选中复选框&#34;添加到目标xxx&#34;&#34;为任何添加的文件夹创建组& #34;(我没有打扰无用的Contents.json文件)。
    • 在首次构建期间检查新警告,如果多个图像具有相同的名称(并以一致的方式重命名)。
    • 对于App图标和启动图像集&#34;不要使用资产目录&#34;在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
    
    • 删除最后一个&#34;,&#34;来自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