在iOS中加载图片的方式有多种,通过OC的方式主要有imageName:和imageWithContentsOfFile:两种。这两种方式如何使用,以及他们之间的区别是什么呢?下面通过两个小示例来详细讲解。
一、imageNamed:和imageWithContentsOfFile:的使用
1、imageNamed:
新建一个工程,将准备好的图片拖入项目中Assets.xcassets文件夹中,具体步骤如下图所示:
来到ViewController.m文件,在viewDidLoad方法中加载图片:
UIImageView *imageView = [[UIImageView alloc] init]; // 创建imageView对象
imageView.frame = CGRectMake(0, 0, 265, 395); // 设置imageView的尺寸
imageView.center = self.view.center; // 让图片在中间位置显示
imageView.image = [UIImage imageNamed:@"0009"]; // 加载图片
[self.view addSubview:imageView]; // 显示图片
显示结果如下:
2、imageContentsOfFile:
与上面一样,新建一个工程,然后再将图片资源拖入到项目中。与上面不一样的是,这次图片资源不是拖入Assets.xcassets文件夹中,而是拖入到Supporting Files文件夹中,原因后面再做解释。详细步骤如下图所示:
需要注意的是,在拖入图片的过程中Xcode会弹出一个"Choose options for adding these files"的提示框,一般按照上图所示选择默认勾选项就可以了(其中Create groups表示在项目中创建虚拟文件夹,而Create folder references表示创建实际文件夹),如果不勾选的话,会出问题。图片资源拖入完成以后效果如下图所示:
通过imageWithContentOfFile:的方式加载图片资源的代码如下:
UIImageView *imageView = [[UIImageView alloc] init]; // 创建imageView对象
imageView.frame = CGRectMake(0, 0, 265, 395); // 设置imageView的尺寸
imageView.center = self.view.center; // 让图片在中间位置显示
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"0010" ofType:@"jpg"]; // 获取图片资源所在路径
imageView.image = [UIImage imageWithContentsOfFile:imagePath]; // 加载图片
[self.view addSubview:imageView]; // 显示图片
运行结果如下图所示:
二、imageNamed:和imageWithContentsOfFile:的区别
1、打包方式上的不同
项目完成以后,所有的图片资源会被一起打包成ipa文件发布到AppStore,拖入Assets.xcassets文件夹中的图片最后会被打包成一个Assets.car文件,我们不能根据路径读取图片。而拖入Supporting Files文件夹中的图片可以根据路径读取。另外,从某种程度上讲,拖入Assets.xcassets文件夹中的图片因为被打包成了Assets.car文件,可以得到一定程度上的保护,以防止盗图(之所以说是一定程度,是因为我们依然可以通过其他手段解压相关图片)。而拖入Supporting Files文件夹中的图片则直接暴露在外面。可以通过下面一系列图片来验证我们的一些结论(验证方法见:如何快速定位模拟器中的沙盒存放路径):
点击"Bundle"——"Application",然后找到当前项目:
右击当前项目,选择"显示包内容",可以看到我们拖入到Assets.xcassets文件夹中名称为0009.jpg的图片没有了,其实他被打包到Assets.car文件夹里面去了。而拖入到Supporting Files文件夹中名称为0010.jpg的图片可以直接查看:
我们可以借助iOS images Extractor插件来验证名称为0009.jpg的图片是否被打包到Assets.car文件中:
2、出于内存和程序性能方面的考虑
除了像上面提到的打包方式上的差别之外,还有另外一个重要的区别,就是从内存和程序性能方面的考虑。为了说明这个问题,我们需要借助另外一个小程序来作进一步的说明。
新建一个工程,分两步来讨论通过不同的方式加载图片所表现出来的程序性能的问题。
通过imageNamed:方法来加载图片。首先将准备好的素材拖入到项目中的Assets.xcassets文件夹当中,然后通过imageNamed:方法来加载:
加载图片的代码如下:
// 抽取加载图片重复的代码
- (NSArray *)loadImagesWithImagePrefixName:(NSString *)prefixName count:(int)count {
NSMutableArray<UIImage *> *images = [NSMutableArray array]; // 创建一个可变数组,用于装载图片
for (int i = 0; i < count; i++) {
NSString *imageName = [NSString stringWithFormat:@"%@_%d", prefixName, i + 1]; // 获取图片的名称
UIImage *image = [UIImage imageNamed:imageName]; // 加载图片
[images addObject:image]; // 将图片添加到可变数组中
}
return images; // 返回数组
}
运行程序来查看内存使用情况:
我们可以看到站立情况下,内存始终是29M左右。点击小招以后内存上升到35.8M,点击大招以后,内存上升到57.2M。这主要是因为每点击一次不同的招数,系统就会加载更多的图片,因此造成内存使用量增加。
重点在于点击停止按钮以后。点击停止按钮所执行的代码如下:
// 游戏结束
- (IBAction)gameOver {
self.standImages = nil;
self.smallImages = nil;
self.bigImages = nil;
self.imageView.animationImages = nil;
}
理论上讲,我们点击停止按钮以后,保存在数组中的图片资源被清空,内存使用量应该下降,但事实上却并没有:
产生上述问题的主要原因是,通过imageNamed:方法加载的图片,其图片在使用完成后,并不会立即被释放掉,具体释放时间由系统决定。因此,这种加载方法,适用于图片小、数量少,且经常使用的图片处理场合。
通过imageWithContentsOfFile:方法来加载图片。和上面一样,将准备好的素材拖入到项目中,不过这次不是拖入到Assets.xcassets文件夹,而是直接拖入到Supporting Files文件夹中,具体情况如下图所示:
修改加载图片的代码:
// 抽取加载图片重复的代码
- (NSArray *)loadImagesWithImagePrefixName:(NSString *)prefixName count:(int)count {
NSMutableArray<UIImage *> *images = [NSMutableArray array]; // 创建一个可变数组,用于装载图片
for (int i = 0; i < count; i++) {
NSString *imageName = [NSString stringWithFormat:@"%@_%d", prefixName, i + 1]; // 获取图片的名称
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"png"]; // 获取图片在资源包中的路径
UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; // 加载图片
[images addObject:image]; // 将图片添加到可变数组中
}
return images; // 返回数组
}
修改完成以后运行程序,点击小招、大招,程序运行时占用内存的情况与通过imageNamed:方式加载图片运行时的情况是一样的。但是,当我们点击停止按钮以后,发现内存使用量又降为29.4M左右了:
这个主要是因为,通过imageWithContentsOfFile:方法加载的图片可以快速的手动释放。
通过这个示例,我们基本上可以总结出imageNamed:和imageWithContentsOfFile:这两个方法的使用场合:
1、imageNamed:方法适用于经常使用,并且图片小、数量少的场合,方便快速加载;
2、imageWithContentsOfFile:方法适用于图片比较大,并且图片数量非常多的场合,此时需要考虑程序的性能。
完整的代码:https://github.com/enricashi/loadImages
第一种方法:imageNamed:
imageNamed的优点在于可以缓存已经加载的图片。这种方法会首先在系统缓存中根据指定的名字寻找图片,如果找到了就返回。如果没有在缓存中找到图片,该方法会从指定的文件中加载图片数据,并将其缓存起来,然后再把结果返回。对于同一个图像,系统只会把它Cache到内存一次,这对于图像的重复利用是非常有优势的。例如:你需要在 一个TableView里重复加载同样一个图标,那么用imageNamed加载图像,系统会把那个图标Cache到内存,在Table里每次利用那个图 像的时候,只会把图片指针指向同一块内存。这种情况使用imageNamed加载图像就会变得非常有效。
第二种方法和第三种方法本质是一样的:imageWithContentsOfFile:和imageWithData:
而imageWithContentsOfFile方法只是简单的加载图片,并不会将图片缓存起来,图像会被系统以数据方式加载到程序。当你不需要重用该图像,或者你
需要将图像以数据方式存储到数据库,又或者你要通过网络下载一个很大的图像时,可以使用这种方式。
如何选择
如果加载一张很大的图片,并且只使用一次,那么就不需要缓存这个图片。这种情况imageWithContentsOfFile比较合适,系统不会浪费内存来缓存图片。
然而,如果在程序中经常需要重用的图片,那么最好是选择imageNamed方法。这种方法可以节省出每次都从磁盘加载