1.官方文档的解释
imageNamed: 把image缓存到内存里面,
此方法在系统缓存中查找具有指定名称的图像对象,并返回最适合主屏幕的图像变体。如果匹配的图像对象尚未在缓存中,此方法将定位并从磁盘或可用资产目录加载图像数据,然后返回结果对象。
系统可以在任何时候清除缓存的图像数据以释放内存。仅对缓存中但当前未使用的图像进行清除。
imageWithContentsOfFile:
或initWithContentsOfFile:方法创建一个图像对象,其中初始数据不在包中。
这些方法每次从磁盘加载图像数据,因此不应该使用它们重复加载相同的图像。
缓存加载方式(imageNamed) :
使用imageNamed这个方法生成的UIImage对象,会在应用的bundle中寻找图片,如果找到则Cache到系统缓存中,作为内存的cache,而程序员是无法操作cache的,只能由系统自动处理,如果我们需要重复加载一张图片,那这无疑是一种很好的方式,因为系统能很快的从内存的cache找到这张图片,但是试想,如果加载很多很大的图片的时候,内存消耗过大的时候,就会会强制释放内存,即会遇到内存警告(memory warnings).由于在iOS系统中释放图片的内存比较麻烦,所以冲易产生内存泄露。
总结下:
何时用imageNamed : 图片资源反复使用到,如按钮背景图片的蓝色背景,这些图片要经常用到,而且占用内存少 button背景图片的蓝色背景。这些图片要常常常使用到,并且占用内存少
不能用 imageNamed : 图片资源较大,加载到内存后,比较耗费内存资源 ,图片一般只使用一次。
(1)图片一般仅仅使用一次。如一些用户的照片资源
(2)图片资源较大,载入到内存后,比較耗费内存资源
非缓存加载方式 (imageWithContentsOfFile) :
相比上面的imageNamed这个方法要写的代码多了几行,使用imageWithContentsOfFile的方式加载的图片,图片会被系统以数据的方式进行加载.返回的对象不会保存在缓存中,一旦对象销毁就会释放内存,所以一般不会因为加载图片的方法遇到内存问题.
imageWithContentsOfFile:比方新手引导界面的图片等等,就适合这样的方式。
imageWithContentsOfFile:使用的封装代码下载
获取MainBundle的图片
+ (UIImage*)imageFromMainBundleFile_zmm:(NSString*)aFileName
{
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", bundlePath,aFileName]];
}
获取bundle中的图片(只需传入图片的名字,方法里面自己查找几倍图,然后获得全路径)
/*
1.只需传图片的名字和Bundle的名字
2.找到当前分辨率的图片
3.当前分辨率的图片不存在的话,则查找其它分辨率图片,从高分辨率开始查找
*/
+ (UIImage *)imageFromBundle:(NSString *)aBundle fileName_zmm:(NSString *)aFileName
{
NSArray *tempArray = [aFileName componentsSeparatedByString:@"."];
//如果文件不包含后缀,返回空
if (tempArray.count != 2)
{
return nil;
}
NSString *name = [tempArray objectAtIndex:0];
NSString *suffix = [tempArray objectAtIndex:1];
//获取图片时不能直接使用包含@x的图片名称
if ([name hasSuffix:@"@x"])
{
return nil;
}
//当前屏幕倍数
NSInteger currentScale = (NSInteger)[[UIScreen mainScreen] scale];
//获取当前分辨率的图片
UIImage *image = [self p_getImageFromBundle:aBundle
name:name
suffix:suffix
scale_zmm:currentScale];
if (image) {
return image;
}
//如果当前分辨率图片不存在,则查找其它分辨率图片,从高分辨率开始查找
for (NSInteger tempScale = 3;tempScale >= 1;tempScale--)
{
if (tempScale == currentScale)
{
continue;
}
//获取当前分辨率的图片
UIImage *image = [self p_getImageFromBundle:aBundle
name:name
suffix:suffix
scale_zmm:tempScale];
if (image) {
return image;
}
}
return nil;
}
#pragma mark - Private Method
+ (NSString *)p_getFileName:(NSString *)aFile
suffix:(NSString *)aSuffix
scale_zmm:(NSInteger)aScale
{
NSString *result = @"";
switch (aScale)
{
case 3:
{
result = [NSString stringWithFormat:@"%@@%ldx.%@",aFile,(long)aScale,aSuffix];
break;
}
default:
result = [NSString stringWithFormat:@"%@.%@",aFile,aSuffix];
break;
}
return result;
}
+ (UIImage *)p_getImageFromBundle:(NSString *)aBundleName imageFullName_zmm:(NSString *)aImageFullName
{
NSString *mainBundlePath = [[NSBundle mainBundle] bundlePath];
NSString *customBundlePath = [mainBundlePath stringByAppendingPathComponent:aBundleName];
NSString *filePath = [customBundlePath stringByAppendingPathComponent:aImageFullName];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
return image;
}
+ (UIImage *)p_getImageFromBundle:(NSString *)aBundleName
name:(NSString *)aName
suffix:(NSString *)aSuffix
scale_zmm:(NSInteger)aScale
{
//获取当前分辨率的图片
NSString *fileFullName = [self p_getFileName:aName suffix:aSuffix scale_zmm:aScale];
UIImage *image = [self p_getImageFromBundle:aBundleName imageFullName_zmm:fileFullName];
return image;
}
注意!!!!!!!如果是有很多图片 图片又很大 会出现 "内存不足,内存泄露,甚至是程序的崩溃"
而在iOS系统里面释放图像的内存是一件比較麻烦的事情。有可能会造成内存泄漏。比如:当一 个UIView对象的animationImages是一个装有UIImage对象动态数组NSMutableArray,并进行逐帧动画。当使用imageNamed的方式载入图像到一个动态数组NSMutableArray,这将会非常有可能造成内存泄露。
YYImage 的内存处理
YYImage 的核心就是学习imageWithContentsOfFile:的方法原理去实现imageNamed:方法. 达到imageNamed:方法中没有缓存功能, 最终使得不需要图片的时候即可销毁图片对象.
imageWithContentsOfFile 代替 imageNamed
首先看 YYImage 的代码:
+ (YYImage *)imageNamed:(NSString *)name {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return nil;
NSString *res = name.stringByDeletingPathExtension;
NSString *ext = name.pathExtension;
NSString *path = nil;
CGFloat scale = 1;
// If no extension, guess by system supported (same as UIImage).
NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
NSArray *scales = [NSBundle preferredScales];
for (int s = 0; s < scales.count; s++) {
scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = [res stringByAppendingNameScale:scale];
for (NSString *e in exts) {
path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];
if (path) break;
}
if (path) break;
}
if (path.length == 0) return nil;
NSData *data = [NSData dataWithContentsOfFile:path];
if (data.length == 0) return nil;
return [[self alloc] initWithData:data scale:scale];
}
从代码可以看出 [YYImage imageNamed:]这个方法底层是利用通过一定的计算获取到最佳尺寸, 然后枚举图片匹配图片文件名, 拼接成路径后利用NSData创建出UIImage. 本质上和imageWithContentsOfFile:没有啥区别.
imageWithContentsOfFile 的缺点
当我们需要图片的时候就会去沙盒中读取这个图片文件, 转换成UIImage对象来使用. 现在假设一种场景:
image@2x.png 图片占用 5kb 的内存
image@2x.png 在多个界面都用到, 且有7处会同时显示这个图片
通过代码分析就可以知道 Resource 这个方式在这个情景下会占用 5kb/个 X 7个 = 35kb 内存. 然而, 在 ImageAssets 方式下, 全部取自字典缓存中的UIImage, 无论有几处显示图片, 都只会占用 5kb/个 X 1个 = 5kb 内存. 此时 Resource 占用内存将会更大.
由于 YYImage 的核心就是利用imageWithContentsOfFile:代替imageNamed:, 所以这也是 YYImage 的缺陷之处
imageName 的缺点
第一次读取的图片保存到缓冲区, 然后永不销毁. 如果这个图片过大, 占用几百 kb, 这一块的内存将不会释放, 必然导致内存的浪费, 而且这个浪费的周期与APP的生命周期同步.