概述:
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];
}