Kealdish's Studio.

UIImage加载图片方式的研究

字数统计: 1.3k阅读时长: 4 min
2015/10/20 Share

常用的UIImage加载图片的方式有以下几种:

  • [UIImage imageNamed:name]
  • [UIImage imageWithContentsOfFile:name]
  • [UIImage imageWithData:data]
  • [UIImage imageWithCGImage:imageRef]
  • [UIImage imageWithCIImage:obj]

以上方法中imageNamed:imageWitData:应用程序会对其自动缓存,不过,它们缓存的实现方式并不相同,后面会有详细说明。除去这两个方法以外剩下的三个方法默认情况下是不会产生缓存的,这三个方法的主要区别在于它们的数据源:imageWithContentsOfFile:从指定文件中创建对象,imageWithCGImage:以CGImageRef来创建对象,imageWithCIImage:以CIImage对象来创建对象。
最后两种方法平时用的相对会少一些,简要解释一下CGImage与CIImage的区别:CGImageRef只能代表位图,如果你需要与bitmap数据打交道,无疑CGImage是非常合适的选择。CGImageRef以CG开头就不难想到CGImageRef的相关操作都需要在Core Graphics中进行,比如混合、遮罩等等。CIImage以CI开头,即Core Image,不难理解CIImage是底层的数据对象,它通常包含了与它相关的图像数据,而不是一个图像。默认情况下,CIImage对象是不会被绘图系统渲染的,除非是得到明确的指令。这种机制(“lazy evaluation”)允许核心绘图系统尽可能高效地运行。CIImage通常被运用在GPU优化图像滤镜算法当中。

UIImage缓存原理

1.[UIImage imageNamed:name]
我通过查看imageNamed方法的调用栈以及查阅苹果相关文档后对UIImage的缓存实现有了清晰的认识。
当调用imageNamed方法时,该方法会去内存缓存里去查找与参数一致的image对象并且返回最合适大小的image对象,如果没有找到,该方法则会去本地磁盘中查找然后加载图片并返回image对象,同时将image对象缓存到系统缓存中,以便下次重复使用。更底层一点的解释,当返回image对象时,并未对image的图片数据进行解码。它的解码过程发生在UIImage对象第一次显示到屏幕上的时候,而image对象的缓存也发生在这时候。当解码完成image显示在屏幕上后,应用程序会将image的解码结果保存到缓存中。通常缓存会在收到内存警告时才会被清空。

2.[UIImage imageWithData:data]
在查看imageWithData方法的调用栈时发现了有意思的东西。通过二进制数据创建image对象时,实际上在底层调用的是ImageIO/ImageIO.hCGImageSourceCreateWithData()方法。该方法的第二个参数可以传入key为kCGImageSourceShouldCache的键值对,它的值是CFBooleanRef类型的,默认情况下,在64位机器上它的值为kCFBooleanTrue,而在32位机器上它的值为kCFBooleanFalse。也就是说在64位机器上是会缓存的,而在32位机器上则是不会缓存的。与imageNamed方法类似,图片会在第一次显示到屏幕上时才会进行解码,随后再被缓存到CGImage里面。依据是CGImageSourceCreateWithData()方法第二个参数可以传入key为kCGImageSourceShouldCacheImmediately的键值对,默认情况下它的值是kCFBooleanFalse。

UIImage不缓存

  • [UIImage imageWithContentsOfFile:name]
    与前面两个方法略有不同,该方法是同步的(synchronous)。当在主线程(UI)中调用该方法时,会阻塞主线程并从磁盘中加载图片数据,若磁盘数据较大会造成卡顿或者延迟。通常的解决方法是另开一个线程异步完成磁盘加载图片数据的任务,然后在主线程中刷新UI。下面代码给出了一个例子:
1
2
3
4
5
6
7
8
9
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
dispatch_async(dispatch_get_main_queue(), ^{
[self.button setBackgroundImage:image forState:UIControlStateNormal];
});
});

当图片显示在屏幕上时,系统并不会对其进行缓存。当图片数据被加载到内存中,它会被标记为可清除(purgeable)。如果数据被清除了且需要再次加载,image对象会再次从指定的文件路径获取数据并加载进内存中。根据它的实现原理,通常该方法的使用场景是图片不需要重复展示,或者图片的数据较大会造成内存警告。当然,这并不是说imageWithContentsOfFile:一定比imageNamed:方法高效,imageWithContentsOfFile:方法的问题在于它会将图片文件全尺寸展示在屏幕上即使是512*512的图片都要占到1M多的内存,加载会影响程序的性能,造成不好的用户体验。
针对图片尺寸太大带来的性能问题,比较好的解决方案是用CGImageSource的有关方法,将图片的尺寸缩减到适合的尺寸,减小数据大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import <ImageIO/ImageIO.h>
NSURL *imageFileURL = [NSURL fileURLWithPath:...];
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageFileURL, NULL);
if (imageSource == NULL) {
// Error loading image
...
return;
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], (NSString *)kCGImageSourceShouldCache,
nil];
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (CFDictionaryRef)options);
if (imageProperties) {
NSNumber *width = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
NSNumber *height = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
NSLog(@"Image dimensions: %@ x %@ px", width, height);
CFRelease(imageProperties);
}
CFRelease(imageSource);

CATALOG
  1. 1. UIImage缓存原理
  2. 2. UIImage不缓存