前言
iOS 图片存放的 3 种方式如下
- Image.xcassets
- 图片直接加入工程中作为Resource
- 使用Bundle文件
Image.xcassets
- 创建
.xcassets
,以Image Set
形式管理图片,添加图片后会生成对应的content.json
文件 - 加入
@2x
和@3x
等倍图后,打包后以Assets.car
的形式存在, - 使用
[UIImage imageNamed:@"xxx"]
方式读取图片,可以使用图片缓存 —— 相当于创建了一个key-value
的字典,key
为图片名,value
为图片对象。创建图片对象后,该对象被加入到NSCache
中(解码后的Image Buffer
),直到收到内存警告的时候,才会释放不在使用的图片对象。 因此,对于需要在多处显示的图片,其对应的UIImage
对象只会被创建一次(不考虑内存警告时的回收),减少内存消耗。
图片直接加入工程中作为Resource
- 读取方式:创建图片
Resource
文件夹,直接将图片加入到工程中,使用如下方式读取图片
NSString *path = [NSBundle.mainBundle pathForResource:@"xxx" type:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:path];
-
特性:在
Resource
的图片管理方式中, 所有的图片创建都是通过读取文件数据得到的, 读取一次文件数据就会产生一次NSData
以及产生一个UIImage
。 当图片创建好后销毁对应的NSData
,当UIImage
的引用计数器变为 0 的时候自动销毁UIImage
,这样的话就可以保证图片不会长期地存在在内存中 -
使用场景:由于这种方法的特性, 所以
Resource
的方法一般用在图片数据很大, 图片一般不需要多次使用的情况,比如说引导页背景(图片全屏) -
优势:图片不会长期保存在内存当中, 所以不会有很多的内存浪费。同时, 大图一般不会长期使用, 而且大图占用内存一般比小图多了好多倍, 所以在减少大图的内存占用中,
Resource
做的非常好
使用Bundle文件
Bundle
即资源文件包,将许多图片,XIB
,文本文件组织在一起,打包成一个Bundle
文件,方便在其他项目中引用包内的资源。Bundle
文件是静态的,不参与项目的编译,Bundle
包中不能包含可执行的文件,它仅仅是作为资源,被解析成为特定的二进制数据。- 优势:
Bundle
中文件不参与项目编译,不影响App包的大小(可用于App的瘦身); 使用bundle
方式方便对文件进行管理,方便在其他项目中引用包内的资源。 - 使用场景:较大的图片,或者使用频率较低的图片
- 读取方式:使用
imageWithContentsOfFile
进行读取,如下方法1;也可以对UIImage
进行扩展,如下方法2
- 使用
imageWithContentsOfFile
读取
/// BSKDefine.h
// bundle path
#define STBundle_Name @"SafeToolResource.bundle"
#define STBundle_Path [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:STBundle_Name]
#define STBundle [NSBundle bundleWithPath:STBundle_Path]
/// usage
#import "BSKDefine.h"
UIImageView * headerBgImgView = [[UIImageView alloc] init];
headerBgImgView.image = [UIImage imageWithContentsOfFile:[SecKill_BUNDLE pathForResource:@"xxxx" ofType:@"png"]];
- 对
UIImage
进行扩展,创建UIImage+BSKResources
类
/// UIImage+BSKResources.h
NS_ASSUME_NONNULL_BEGIN
@interface UIImage (BSKResources)
+ (UIImage *)bskImageNamed:(NSString *)imageName InBundleName:(NSString *)bundleName;
@end
NS_ASSUME_NONNULL_END
/// UIImage+BSKResources.m
#import "UIImage+BSKResources.h"
@implementation UIImage (BSKResources)
+ (UIImage *)bskImageNamed:(NSString *)imageName InBundleName:(NSString *)bundleName
{
NSString *resourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:bundleName];
NSBundle *resourceBundle = [NSBundle bundleWithPath:resourcePath];
return [UIImage imageNamed:imageName inBundle:resourceBundle compatibleWithTraitCollection:nil];
}
@end
/// usage
#import "UIImage+BSKResources.h"
UIImageView * headerBgImgView = [[UIImageView alloc] init];
headerBgImgView.image = [UIImage bskImageNamed:@"xxx" InBundleName:@"BSKResources.bundle"]];
Bundle 和 xcassets 区别
xcassets
里面的图片,只能通过imageNamed
加载。Bundle
还可以通过imageWithContentsOfFile
等方式加载xcassets
里的2x
和3x
,会根据具体设备分发,不会同时包含(App Slicing
),而Bundle
会都包含xcassets
内,可以对图片进行Slicing
,即裁剪和拉伸,Bundle
不支持Bundle
内支持多语言,xcassets
不支持- 此外,使用
imageNamed
创建的UIImage
,会立即被加入到NSCache
中(解码后的Image Buffer
),直到收到内存警告的时候,才会释放不在使用的UIImage
;而使用imageWithContentsOfFile
创建的对象,每次都会重新申请内存,相同图片不会缓存。因此,建议常用的、较小的图,放在xcassets
内管理,而大图、使用频率较低的图,应该放在Bundle
内管理
图片资源文件大小计算
- 扩展阅读 - 干货!京东商城iOS App瘦身实践
在日常开发中,会针对App包体积大小进行优化和瘦身。以往,常常以为本地开发工程中的资源文件大小就是最后安装包中的大小,将本地图片压缩一下就可以瘦身。
而根据 干货!京东商城iOS App瘦身实践 文章观点,很多图片在本地和安装包中的大小差异非常之大,往往相差几倍,甚至有几十倍的。通过调研知道 Apple 为了在优化 iPhone 设备读取 png 图片速度,将 png 转换成 CgBI 非标准的 png 格式
- extra critical chunk (CgBI)
- byteswapped (RGBA -> BGRA) pixel data, presumably for high-speed direct blitting to the framebuffer
- zlib header, footer, and CRC removed from the IDAT chunk
- premultiplied alpha (color' = color * alpha / 255)
苹果的优化对于大多数应用来说都是包大小的负优化,商城也不例外。所以简单的压缩(有损,无损)处理并不能达到很好的瘦身效果。而经过测试,以下文件会被负优化
- 放在根目录下png格式的图片
- 放在Asset Catalog中的png,jpg格式的图片,其中jpg会转成png
而放在根目录下的 jpg,bundle 中的 png 不会被优化,本地图片的大小,就是安装包中资源文件的大小。
其他
截取图片中间部分展示
业务开发中,遇到需要截取图片中间部分进行展示的需求,如上图所示。记录其实现方式如下
self.topBgImgView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,100,100)];
//展示图片中间部分
[_topBgImgView setContentScaleFactor:[[UIScreen mainScreen] scale]];
_topBgImgView.contentMode = UIViewContentModeScaleAspectFill;
_topBgImgView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
_topBgImgView.clipsToBounds = YES;