UIImage caching with Xcode Asset directories

We all know about the mysterious hidden caching mechanism of the UIImage imageNamed: method. The Apple UIImage Class Reference it says:

In low memory situations, image data can be cleared from the UIImage object to free up memory in the system. This cleanup behavior only affects the image data stored inside the UIImage, and not the object itself. When you try to draw an image whose data has been cleared, the image object automatically reloads the data from its source file. However, this additional download step may result in a small performance penalty.

In fact, the image data will not be "cleared of the UIImage object to free up memory in the system," as the documentation shows. Instead, the application receives warnings about memory until it stops "due to memory pressure."
EDIT: When using regular links to image files in your Xcode project, UIImage caching works fine. This is easy when you go to directories of assets that are not issued in memory.

I have implemented a UIScrollView with several UIImageViews to scroll through a long list of images. When scrolling, the following images are loaded and assigned to the UIImageView image property, removing the strong link to the previously saved UIImage.

Due to the caching mechanism of imageNamed: I quickly run out of memory, and the application terminates with 170 MB of dedicated memory.

Of course, there are many interesting solutions for implementing custom caching mechanisms, including overriding the method of the imageNamed: class in a category. Often, the imageWithContentOfFile: class method is used instead, which does not imageWithContentOfFile: image data, as suggested by Apple developers at WWDC 2011.

These solutions are great for regular image files, although you need to get a path and file extension that is not as elegant as I would like.

I use the new Asset Catalogs introduced in Xcode 5, however, to use conditional image loading mechanisms depending on the device and efficient storage of image files. At the moment, there seems to be no direct way to load an image from an asset catalog without using imageNamed: unless I see an obvious solution.

Did you guys figure out the UIImage caching mechanism with resource directories?

I would like to implement a category in UIImage similar to the following:

 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 be to gain more control over the internal UIImage cache and the ability to clear image data in low memory conditions, as described in the documentation when using Asset Directories.

Thanks for reading, and I look forward to your ideas!

+15
ios objective-c cocoa-touch
Nov 12 '13 at 1:10
source share
1 answer

UPDATE: Eviction cache works fines (at least starting with iOS 8.3).

I ran into the same problem (iOS 7.1.1) and I kinda like this @Lukas may be right

There is a high probability that the error is not in Apple ... caching, but in your .. code.

So I wrote a very simple test application (see full source below) where I still see the problem. If you see something wrong , let me know. I know that it really depends on the size of the image. I only see the problem on the 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 



On-site deployment

I wrote some code to save an image resource, but downloaded it using imageWithData: or imageWithContentsOfFile : use xcassets without imageNamed to prevent memory problems?

+4
Apr 28 '14 at 19:31
source share



All Articles