概述:
iOS 開發中,很多app的網絡圖片處理使用SDwebImage或YYKit的YYImage,使用方便、穩定、內部做了很多的優化處理。但也存在一些問題
1、SDWebImage庫提供了一整套的網絡圖片異步下載和緩存機制,還增加了UIImageView、UIButton等的Category,方便顯示網絡圖片。
2、存在的問題
由于網絡圖片一般不會有@2x和@3x之分,通過SDWebImage庫下載的圖片不加以處理就直接顯示,會有一些常見的問題,如像素不對齊。
App中經常使用圓角圖片,一般采用裁剪圖片的方式;但是這些圖片源來自服務器(本地圓角圖片讓UI直接提供就可以了),我們需要在SDWebImage基礎上增加對網絡圓角圖片的處理。
GPU圖層顯示的優化
一、像素不對齊
檢測方式:
以下兩種方式均可發現存在Misaligned Images問題的地方:
- 模擬器調試時,打開模擬器的Debug - Color Misaligned Images菜單選項。最快捷,但僅限模擬器上查看。
- Instrument性能檢測時,選中Core Animation模板,在Display Settings中勾選Color Misaligned Images選項。可針對模擬器和真機,可查看真機上所有應用的像素混合情況。
問題定義:
打開開關后,看到部分視圖會有黃色或洋紅色(Magenta)的圖層標記,代表其像素不對齊。
- 不對齊:視圖或圖片的點數(point),不能換算成整數的像素值(pixel),導致顯示視圖的時候需要對沒對齊的邊緣進行額外混合計算,影響性能。
- 洋紅色:UIView的frame像素不對齊,即不能換算成整數像素值。
- 黃色:UIImageView的圖片像素大小與其frame.size不對齊,圖片發生了縮放造成。
優化方式:
frame像素不對齊
借助ceilf()、floorf()、CGRectIntegral()等將小數點后數據除去即可。
使用floorf時,需要注意是否會因為向下取整而影響視圖的顯示。
像素不對稱齊的元素一般為UILabel或UIImageView。
圖片像素不對齊
圖片是從服務端獲取到的,大小不規則。直接在UIImageView上顯示容易出現像素不對齊。
解決方法:
將下載到的圖片,縮放到與UIImageView對應的尺寸,再顯示出來。
#import "UIImage+Additions.h"
/**
圖片縮放 避免圖片像素不對齊
需要縮放圖片到與UIImageView對應的尺寸,且縮放后的圖片的scale和[UIScreen mainScreen].scale相等,再顯示出來。
圖片的縮放是相對耗時的,放在非主線程,更新圖片在主線程
@param size 要達到的尺寸 一般為UIImageView的size
@return 縮放后的圖片
*/
- (UIImage *)scaleImageWithSize:(CGSize)size{
if (CGSizeEqualToSize(size, self.size)) {
return self;
}
//創建上下文
UIGraphicsBeginImageContextWithOptions(size, YES, [UIScreen mainScreen].scale);
//繪圖
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
//獲取新圖片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
2、像素混合
像素混合是指在某視圖為透明背景色,GPU在渲染視圖時,需要將該視圖和下層視圖混合(Blend)后才能計算出該像素點的實際顏色;這增加了GPU的工作,損耗了性能。
當圖片是透明圖片時,像素混合必然會發生。所以顯示的圖片最好是不透明的。
iPhone模擬器中的Debug ->Color Blended Layers選項 和 Core Animation ->Display Settings ->Color Blended Layers都可以將像素混合的部分顯示出來。
發生了像素混合的區域顯示紅色,正常則顯示綠色。
圖片處理使用Core Graphics實現。在使用UIGraphicsBeginImageContextWithOptions創建上下文時候,opaque默認為YES,表示不透明;
3、圓角圖片的問題
不建議的方案1:通過設置cornerRadius值和masksToBounds=YES實現圓角效果。因為它會觸發GPU的離屏渲染,引起性能問題。模擬器中的Color Offscreen-Rendered可以檢測是否發生離屏渲染(如果出現黃色就發生了離屏渲染)。
不建議的方案2:通過設置view.layer的mask屬性,將另一個layer蓋在view上,也可以實現圓角的效果,但是同樣會觸發離屏渲染,引起性能問題。
建議的方案:使用Core Graphics重新繪制帶圓角的圖片,雖然在顯示上提升了性能,但是增加了繪制的工作,所以要做好異步繪制和緩存工作,盡可能避免重復繪制。使用SD的UIImage+Transform分類方法:
#import<UIImage+Transform.h>
- (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius
corners:(SDRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(nullable UIColor *)borderColor;
二、內存暴增
使用SDWebImage和YYImage下載高分辨率圖時,可能導致內存暴增
用instrument檢測發現是SDWebImage和YYImage對圖像進行解壓縮操作時引起的,那么這兩個框架使用CGBitmapContextCreate的目的是為了優化圖片加載或者從本地加載圖片后的解碼過程,但decodeImageWithImage這個方法用于對圖片進行解壓縮并且緩存起來,以保證tableviews/collectionviews 交互更加流暢,但是如果是加載高分辨率圖片的話,會適得其反,有可能造成上G的內存消耗。對于高分辨率的圖片,應該禁止解壓縮操作,相關的代碼處理為:
解決方式一(推薦):設置SDWebImageOptions
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
解決方式二:SDWebImage5.0+已經沒有此方法
#import <SDWebImage/SDImageCache.h>
#import <SDWebImage/SDWebImageDownloader.h>
// 禁用解壓縮
- (void)loadView{
[[SDImageCache sharedImageCache].config setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
}
//恢復原設置
- (void)dealloc{
[[SDImageCache sharedImageCache].config setShouldDecompressImages:YES];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:YES];
}
此方式既能保證高分辨率圖不crash,也能保證其他地方,普通圖片依舊可以通過解壓縮進行優化。
另外在收到內存警告時,做如下操作:
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application{
// 清空緩存(內存)
[[SDImageCache sharedImageCache] clearMemory];
// 清空磁盤圖片
[[SDImageCache sharedImageCache] clearDiskOnCompletion:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 清空緩存(內存)
[[SDImageCache sharedImageCache] clearMemory];
// 清空磁盤圖片
[[SDImageCache sharedImageCache] clearDiskOnCompletion:nil];
// Dispose of any resources that can be recreated.
}
解決方式三:
使用SDWebimage肯定都會做三件事,一判斷本地是否有這張圖,二有的時候直接從本地取圖片,三沒有的時候去網絡下載。在內部都會使用到下面這個方法
發現這里面對圖片的處理是直接按照原大小進行的,如果幾千是分辨率這里導致占用了大量內存,所以我們需要在這里對圖片做一次等比的壓縮。在UIImage+MultiFormat這個類里面添加如下壓縮方法,
+(UIImage *)compressImageWith:(UIImage *)image
{
float imageWidth = image.size.width;
float imageHeight = image.size.height;
float width = 640;
float height = image.size.height/(image.size.width/width);
float widthScale = imageWidth /width;
float heightScale = imageHeight /height;
// 創建一個bitmap的context
// 并把它設置成為當前正在使用的context
UIGraphicsBeginImageContext(CGSizeMake(width, height));
if (widthScale > heightScale) {
[image drawInRect:CGRectMake(0, 0, imageWidth /heightScale , height)];
}
else {
[image drawInRect:CGRectMake(0, 0, width , imageHeight /widthScale)];
}
// 從當前context中創建一個改變大小后的圖片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
// 使當前的context出堆棧
UIGraphicsEndImageContext();
return newImage;
}
再在上面 UIImage *image = [[UIImage alloc] initWithData:data];代碼后面對圖片進行壓縮
UIImage *image = [[UIImage alloc] initWithData:data];
if (data.length/1024 > 128) {
image = [self compressImageWith:image];
}