当今互联网,无论网页还是 APP,流量占用最大的,多数都是因为图片,越是良好的用户体验,对图片的依赖度越高。但是图片是一把双刃剑,带来了用户体验,吸引了用户注意,却影响了性能,因为网络请求时间会相对比较长。
图片分很多种,比较主流的就是:位图(BMP),jpg(JPEG,有损压缩格式),png(无损压缩格式)等,这三种,按照图片大小和清晰度来看,依次是:BMP > png > jpg。因为 jpg 是有损压缩格式,所以 jpg 图片相对最小。iOS 普遍选择的是 png 来作为最优先选择的图片(苹果官方也是这样建议的)。
不过,有一种图片格式,在大小上比 png 小,图片质量上跟 png 差不多,就是 webP。
简单描述一下,WebP 是 google 创造出的一种图片格式,图片的压缩和解码都由 google 提供的 API 完成(各种语言都有,不过目前好像没看到 js 可以解码 WebP 的),在无损压缩的情况下,比 png 要小 28% 左右。
现在已经被各大浏览器厂商兼容(如:Chrome,Firefox 等),不过苹果的 Safri 还没有兼容这种格式,所以如果 UIWebView 里面含有 WebP 的图片的话,就会显示不出来(但是我们可以通过 NSUrlProtocol 来做处理)。如果要在 APP 中使用得话,我们需要引入 SDWebImage 这个第三方库。
这个第三方库封装得很好,使用起来与我们以前用他来加载网络图片方式一样,如下:
- [imageView sd_setImageWithURL:[NSURL URLWithString:图片路径] placeholderImage:[UIImage imageNamed:@"默认图片"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { }];
不过,我们要深入看看他究竟是怎么实现的。
我们打开:
- SDWebImageDownloaderOperation
这个类继承了 NSOperation,主要使用 NSUrlSession 来下载网络图片,我们来看他下载完成的委托方法:
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
我们截取部分代码块来集中分析一下:
- UIImage *image = [UIImage sd_imageWithData:self.imageData];
- 调试进去:
- UIImage *image;
- NSString *imageContentType = [NSData sd_contentTypeForImageData:data]; //根据数据流的前8位来判断图片类型
- if ([imageContentType isEqualToString:@"image/gif"]) {
- image = [UIImage sd_animatedGIFWithData:data];
- }
- #ifdef SD_WEBP
- else if ([imageContentType isEqualToString:@"image/webp"])
- {
- image = [UIImage sd_imageWithWebPData:data]; //将WebP解码成相应的格式(可能是jpg,png等)
- }
- #endif
- else {
- image = [[UIImage alloc] initWithData:data];
- UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
- if (orientation != UIImageOrientationUp) {
- image = [UIImage imageWithCGImage:image.CGImage
- scale:image.scale
- orientation:orientation];
- }
- }
- + (NSString *)sd_contentTypeForImageData:(NSData *)data {
- uint8_t c;
- [data getBytes:&c length:1];
- switch (c) {
- case 0xFF:
- return @"image/jpeg";
- case 0x89:
- return @"image/png";
- case 0x47:
- return @"image/gif";
- case 0x49:
- case 0x4D:
- return @"image/tiff";
- case 0x52:
- // R as RIFF for WEBP
- if ([data length] < 12) {
- return nil;
- }
- NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
- if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
- return @"image/webp";
- }
- return nil;
- }
- return nil;
- }
里面的 uint8_t 就是取 NSData 的前 8 位,因为图片变换成 NSData 后,是使用得 ASCII 码来表示的,每种图片都含有固定的头信息块。
png 是:89 50 4E 47 0D 0A 1A 0A
bmp 是:42 4D
jpg 是:FF D8 FF
webp 是:52 49 46 46 中间 4 个字符不定 57 45 42 50(翻译过来就是:RIFF 其他 4 个字符 WEBP)
这样来看,上面代码的含义就比较清楚了。
如果想深入了解一下图片格式及组成,这里有一篇不错的文章:
里面封装了将 WebP 解码成其他格式图片的过程。WebP 是采用 VP8 的编码格式。有兴趣可以研究一下具体的算法实现过程,这里有几篇文章介绍 WebP 的压缩算法。
SDWebImage 在对 WebP 做存储的时候,存的是未解码的 NSData,而不是解码后的 NSData,如下代码:
- SDWebImageManager 里面的
- if (options & SDWebImageRefreshCached && image && !downloadedImage) {
- // Image refresh hit the NSURLCache cache, do not call the completion block
- }
- else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage))) {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
- UIImage *transformedImage = [self transformDownloadedImage:downloadedImage imageData:data withURL:url]; //存储以前,是否要将nsdata转换为其他格式的图片对象
- if (transformedImage && finished) {
- BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
- [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
- }
- dispatch_main_sync_safe(^{
- if (strongOperation && !strongOperation.isCancelled) {
- completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
- }
- });
- });
- }
- else {
- if (downloadedImage && finished) {
- [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; //WebP的存储走的是这一步
- }
- dispatch_main_sync_safe(^{
- if (strongOperation && !strongOperation.isCancelled) {
- completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
- }
- });
- }
里面提供了一个委托:
- UIImage *transformedImage = [self transformDownloadedImage:downloadedImage imageData:data withURL:url];
也算是用心良苦,因为可能考虑到 WebP 的解码会耗费一些时间(测试下来发现,120k 左右的 WebP,解码会耗时 30ms 左右),所以提供一个委托,可以选择将 WebP 的 NSData 转换为 png 或者 jpg 之后,再存储到内存,再存储到磁盘。
不过,时间与空间就像鱼和熊掌,不可兼得,如果选择节省时间,就不可避免的要占用更大的空间。到底选时间还是选空间,仁者见仁智者见智吧。
把 WebP 说得这么天花乱坠,但是 WebP 也是有自己的劣势的:
关于 WebP 和 jpg 的图片大小来比较的话,因为 WebP 是支持无损和有损压缩的,而 jpg 是有损压缩的格式,所以如果同样的图片都做有损压缩,WebP 是比 jpg 要小的。
这里有篇不错的介绍 WebP 的文章:
来源: http://www.cnblogs.com/lizheng114/p/6582352.html