使用没有 imageNamed 的 xcassets 来防止内存问题? [英] use xcassets without imageNamed to prevent memory problems?

查看:20
本文介绍了使用没有 imageNamed 的 xcassets 来防止内存问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

according to the apple documentation it is recommended to use xcassets for iOS7 applications and reference those images over imageNamed.

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

But as far as I'm aware, there were always problems with imageNamed and memory.

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

So I made a short test application - referencing images out of the xcassets catalogue with imageNamed and started the profiler ... the result was as expected. Once allocated memory wasn't released again, even after I removed the ImageView from superview and set it to nil.

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

I'm currently working on an iPad application with many large images and this strange imageView behavior leads to memory warnings.

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

But in my tests I wasn't able to access xcassets images over imageWithContentsOfFile.

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

So what is the best approach to work with large images on iOS7? Is there a way to access images from the xcassets catalogue in another (more performant) way? Or shouldn't I use xcassets at all so that I can work with imageWithContentsOfFile?

感谢您的回答!

推荐答案

更新: 缓存驱逐效果很好(至少从 iOS 8.3 开始).

UPDATE: Cache eviction works fines (at least since iOS 8.3).

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

I decided to go with the "new Images.xcassets" from Apple, too. Things started to go bad, when I had about 350mb of images in the App and the App constantly crashed (on a Retina iPad; probably because of the size of the loaded images).

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

I have written a very simple test app where I load the images in three different types (watching the profiler):

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

  1. imageNamed: loaded from an asset: images never gets released and the app crashes (for me I could load 400 images, but it really depends on the image size)

imageNamed:(通常包含在项目中):内存使用率很高,偶尔(> 400 张图片)我看到对 didReceiveMemoryWarning 的调用:代码>,但应用程序运行良好.

imageNamed: (conventionally included to the project): The memory usage is high and once in a while (> 400 images) I see a call to didReceiveMemoryWarning:, but the app is running fine.

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

imageWithContentsOfFile([[NSBundle mainBundle] pathForResource:...): The memory usage is very low (<20mb) because the images are only loaded once at a time.

我真的不会把所有事情都归咎于 imageNamed: 方法的缓存,因为如果你必须一次又一次地显示你的图像,缓存是一个好主意,但苹果这样做有点遗憾不为资产实施它(或没有记录它未实施).在我的用例中,我将使用非缓存 imageWithData,因为用户不会再次看到图像.

I really would not blame the caching of the imageNamed: method for everything as caching is a good idea if you have to show your images again and again, but it is kind of sad that Apple did not implement it for the assets (or did not document it that it is not implemented). In my use-case, I will go for the non-caching imageWithData because the user won't see the images again.

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

As my app is almost final and I really like the usage of the loading mechanism to find the right image automatically, I decided to wrap the usage:

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

脚本:

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

  • 从 json 输出中删除最后一个,"逗号,因为我没有费心在这里手动操作.
  • 我使用以下应用生成 json-model-access 代码:https://itunes.apple.com/de/app/json-accelerator/id511324989?mt=12(目前免费),前缀为 IMGA
  • 我使用方法 swizzling 编写了一个不错的类别,以便不更改正在运行的代码(并希望很快删除我的代码):
    • Remove the last "," comma from json output as I did not bother to do it manually here.
    • I have used the following app to generate json-model-access code: https://itunes.apple.com/de/app/json-accelerator/id511324989?mt=12 (currently free) with prefix IMGA
    • I have written a nice category using method swizzling in order to not change running code (and hopefully removing my code very soon):
    • (并非所有设备和回退机制的实现都已完成!!)

      (implementation not complete for all devices and fallback mechanisms!!)

      #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
      

      这篇关于使用没有 imageNamed 的 xcassets 来防止内存问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆