UPDATE: Eviction cache works fines (at least starting with iOS 8.3).
I decided to go with the "new Images.xcassets" from Apple. Everything became bad when I had about 350 mb of images in the application and the application that constantly crashed (on the iPad Retina, probably due to the size of the downloaded images).
I wrote a very simple test application in which I upload images in three different types (I look at the profiler):
imageNamed: loaded from an asset: images are never freed and the application crashes (for me I could upload 400 images, but it really depends on the size of the image)
imageNamed: (usually included in a project): memory usage is high and from time to time (> 400 images) I see a call to didReceiveMemoryWarning: but the application is working fine.
imageWithContentsOfFile([[NSBundle mainBundle] pathForResource:...) : memory usage is very low (<20mb) because images are loaded only once at a time.
I really would not blame the caching of the imageNamed: method imageNamed: for everything, since caching is a good idea if you have to show your images again and again, but it is sad that Apple did not implement it for assets (or document that it was not implemented). In my use case, I will move on to caching imageWithData because the user will not see the image again.
Since my application is almost final, and I really like using the download mechanism to find the correct image automatically, I decided to wrap the usage:
- I removed images.xcasset from the copy-target-stage and added all the images “again” to the project and the copy phase (just add the top-level folder to Images.xcassets directly and make sure the “Add to target xxx” and “Create groups” checkboxes are checked for any added folders "(I didn't worry about the useless Contents.json files).
- During the first build, check for new warnings if multiple images have the same name (and rename them sequentially).
- For application icons and startup images, set “Do not use the asset catalog” for the general purpose of the project and specify them manually there.
- I wrote a shell script to generate a json model from all Contents.json files (so that the information is used as Apples in the resource access code)
Script:
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
- Remove the last "," comma from json output since I didn’t do it manually here.
- I used the following application to generate the json model access code: https://itunes.apple.com/de/app/json-accelerator/id511324989?mt=12 (currently free) with the IMGA prefix
- I wrote a nice category using the swizzling method so as not to change the current code (and hopefully deleted my code very quickly):
(implementation is not completed for all devices and backup 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
theguy May 02 '14 at 22:54 2014-05-02 22:54
source share