简体   繁体   English

使用Xcode资产目录进行UIImage缓存

[英]UIImage caching with Xcode Asset Catalogs

We all know about the mysterious behind-the-scenes caching mechanism of UIImage's imageNamed: method. 我们都知道UIImage的imageNamed:方法的神秘的幕后缓存机制。 In Apple's UIImage Class Reference it says: 在Apple的UIImage类参考中,它说:

In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. 在内存不足的情况下,可以从UIImage对象中清除图像数据以释放系统上的内存。 This purging behavior affects only the image data stored internally by the UIImage object and not the object itself. 此清除行为仅影响UIImage对象内部存储的图像数据,而不影响对象本身。 When you attempt to draw an image whose data has been purged, the image object automatically reloads the data from its original file. 当您尝试绘制其数据已被清除的图像时,图像对象会自动从其原始文件重新加载数据。 This extra load step, however, may incur a small performance penalty. 然而,这个额外的负载步骤可能会导致很小的性能损失。

In fact, image data will not be "purged from a UIImage object to free up memory on the system" as the documentation suggests, however. 实际上,正如文档所示,图像数据不会“从UIImage对象中清除以释放系统上的内存”。 Instead, the app receives memory warnings until it quits "due to memory pressure". 相反,应用程序会收到内存警告,直到它“因内存压力”退出。
EDIT: When using the conventional image file references in your Xcode project, the UIImage caching works fine. 编辑:在Xcode项目中使用传统的图像文件引用时,UIImage缓存工作正常。 It's just when you transition to Asset Catalogs that the memory is never released. 只是当您转换到资产目录时,内存永远不会被释放。

I implemented a UIScrollView with a couple of UIImageViews to scroll through a long list of images. 我实现了一个带有几个UIImageViews的UIScrollView来滚动浏览一长串图像。 When scrolling, the next images are being loaded and assigned to the UIImageView's image property, removing the strong link to the UIImage it has been holding previously. 滚动时,正在加载下一个图像并将其分配给UIImageView的image属性,删除之前保存的UIImage的强链接。

Because of imageNamed: 's caching mechanism, I quickly run out of memory, though, and the app terminates with around 170 MB memory allocated. 由于imageNamed:的缓存机制,我很快就会耗尽内存,应用终止时分配了大约170 MB的内存。

Of course there are plenty of interesting solutions around to implement custom caching mechanisms, including overriding the imageNamed: class method in a category. 当然,有很多有趣的解决方案可以实现自定义缓存机制,包括覆盖类别中的imageNamed: class方法。 Often, the class method imageWithContentOfFile: that does not cache the image data is used instead, as even suggested by Apple developers at the WWDC 2011. 通常,使用不缓存图像数据的类方法imageWithContentOfFile:正如Apple开发人员在WWDC 2011上所建议的那样。

These solutions work fine for regular image files, although you have to get the path and file extension which is not quite as elegant as I would like it to be. 这些解决方案适用于常规图像文件,但您必须获得不像我希望的那样优雅的路径和文件扩展名。

I am using the new Asset Catalogs introduced in Xcode 5, though, to make use of the mechanisms of conditionally loading images depending on the device and the efficient image file storage. 我使用Xcode 5中引入的新资产目录 ,以利用根据设备和高效图像文件存储有条件地加载图像的机制。 As of now, there seems to be no straight forward way to load an image from an Asset Catalog without using imageNamed: , unless I am missing an obvious solution. 截至目前,似乎没有直接的方法从资产目录加载图像而不使用imageNamed: ,除非我错过了一个明显的解决方案。

Do you guys have figured out a UIImage caching mechanism with Asset Catalogs? 你们是否已经找到了资产目录的UIImage缓存机制?

I would like to implement a category on UIImage similar to the following: 我想在UIImage上实现一个类似于以下内容的类别:

static NSCache *_cache = nil;

@implementation UIImage (Caching)

+ (UIImage *)cachedImageNamed:(NSString *)name {
    if (!_cache) _cache = [[NSCache alloc] init];

    if (![_cache objectForKey:name]) {
        UIImage *image = ???; // load image from Asset Catalog without internal caching mechanism
        [_cache setObject:image forKey:name];
    }

    return [_cache objectForKey:name];
}

+ (void)emptyCache {
    [_cache removeAllObjects];
}

@end

Even better would of course be a way to have more control over UIImage 's internal cache and the possibility to purge image data on low memory conditions as described in the documentation when using Asset Catalogs. 当然,更好的方法是更好地控制UIImage的内部缓存,以及在使用资产目录时文档中所述的低内存条件下清除图像数据的可能性。

Thank you for reading and I look forward to your ideas! 感谢您的阅读,我期待您的想法!

UPDATE: Cache eviction works fines (at least since iOS 8.3). 更新:缓存逐出罚款(至少从iOS 8.3开始)。

I am running into the same issue (iOS 7.1.1) and I kind of though that @Lukas might be right 我遇到了同样的问题(iOS 7.1.1),我觉得@Lukas可能是对的

There is a high probability that the mistake is not inside Apple's ... caching but in your .. code. 这个错误很可能不在Apple的......缓存中,而是在你的..代码中。

Therefore I have written a very simple Test App (view full source below) where I still see the issue. 因此,我写了一个非常简单的测试应用程序(查看下面的完整源代码),我仍然看到了这个问题。 If you see anything wrong with it, please let the me know about it. 如果您发现任何问题 ,请让我知道。 I know that it really depends on the image sizes. 我知道这取决于图像尺寸。 I only see the issue on an iPad Retina. 我只在iPad Retina上看到这个问题。

  @interface ViewController ()

  @property (nonatomic, strong) UIImageView *imageView;
  @property (nonatomic, strong) NSArray *imageArray;
  @property (nonatomic) NSUInteger   counter;

  @end

  @implementation ViewController

  - (void)viewDidLoad
  {
      [super viewDidLoad];

      self.imageArray = @[@"img1", ...  , @"img568"];
      self.counter = 0;

      UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
      self.imageView = [[UIImageView alloc] initWithImage: image];
      [self.view addSubview: self.imageView];

      [self performSelector:@selector(loadNextImage) withObject:nil afterDelay:1];
  }

  - (void)didReceiveMemoryWarning
  {
      [super didReceiveMemoryWarning];
      NSLog(@"WARN: %s", __PRETTY_FUNCTION__);
  }


  - (void)loadNextImage{

      self.counter++;
      if (self.counter < [self.imageArray count])
      {
          NSLog(@"INFO: %s - %lu - %@",
                __PRETTY_FUNCTION__,
                (unsigned long)self.counter,
                [self.imageArray objectAtIndex:self.counter]);
          UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
          self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
          [self.imageView setImage:image];

          [self performSelector:@selector(loadNextImage) withObject:nil afterDelay:0.2];
      } else
      {
          NSLog(@"INFO: %s %@", __PRETTY_FUNCTION__, @"finished");
          [self.imageView removeFromSuperview];
      }
  }
  @end

Inplace Implementation 就地实施

I wrote some code to keep the image asset but load it with imageWithData: or imageWithContentsOfFile : use xcassets without imageNamed to prevent memory problems? 我写了一些代码来保存图像资产,但用imageWithData:imageWithContentsOfFile加载它: 使用没有imageNamed的xcassets来防止内存问题?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM