iOS效能之WebP,ioswebp
當今互連網,無論網頁還是APP,流量佔用最大的,多數都是因為圖片,越是良好的使用者體驗,對圖片的依賴度越高。但是圖片是一把雙刃劍,帶來了使用者體驗,吸引了使用者注意,卻影響了效能,因為網路請求時間會相對比較長。
圖片分很多種,比較主流的就是:位元影像(BMP),jpg(JPEG,有損壓縮格式),png(無損壓縮格式)等,這三種,按照圖片大小和清晰度來看,依次是:BMP > png > jpg。因為jpg是有損壓縮格式,所以jpg圖片相對最小。iOS普遍選擇的是png來作為最優先選擇的圖片(蘋果官方也是這樣建議的)。
不過,有一種圖片格式,在大小上比png小,圖片品質上跟png差不多,就是WebP。
什麼是WebP?
簡單描述一下,WebP是google創造出的一種圖片格式,圖片的壓縮和解碼都由google提供的API完成(各種語言都有,不過目前好像沒看到js可以解碼WebP的),在無損壓縮的情況下,比png要小28%左右。
現在已經被各大瀏覽器廠商相容(如:Chrome,Firefox等),不過蘋果的Safri還沒有相容這種格式,所以如果UIWebView裡面含有WebP的圖片的話,就會顯示不出來(但是我們可以通過NSUrlProtocol來做處理)。如果要在APP中使用得話,我們需要引入SDWebImage這個第三方庫。
SDWebImage使用WebP
這個第三方庫封裝得很好,使用起來與我們以前用他來載入網狀圖片方式一樣,如下:
[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]; } }
- 我們來說下sd_contentTypeForImageData 這個方法,如下:
+ (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)
這樣來看,上面代碼的含義就比較清楚了。
如果想深入瞭解一片格式及組成,這裡有一篇不錯的文章:
http://blog.csdn.net/hherima/article/details/45846901
- 我們再來看看 sd_imageWithWebPData 這個方法
裡面封裝了將WebP解碼成其他格式圖片的過程。WebP是採用VP8的編碼格式。有興趣可以研究一下具體的演算法實現過程,這裡有幾篇文章介紹WebP的壓縮演算法。
https://developers.google.com/speed/webp/docs/compression
http://blog.csdn.net/leixiaohua1020/article/details/12760173
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也是有自己的劣勢的:
最後
關於WebP和jpg的圖片大小來比較的話,因為WebP是支援無損和有損壓縮的,而jpg是有損壓縮的格式,所以如果同樣的圖片都做有損壓縮,WebP是比jpg要小的。
這裡有篇不錯的介紹WebP的文章:
https://isux.tencent.com/introduction-of-webp.html