UIImageView 性能优化

引言:

切记: 不要动态修改 cornerRadius 之类的图层渲染相关的属性, 严重影响性能。 通过分装一个UIImage 分类来实现 图像的性能优化。


# 一. 提高圆角图像性能

如果圆角的创建方式使用的是 layer.cornerRadius,表格中使用过多圆角会严重影响性能。

# 1. 绘图

使用绘图,重新绘制一个新的图像。

注意: 要将上述操作 放到异步队列中执行, 在主队列中完成回调 ,返回新的图像,避免图像过大绘制缓慢。

声明:

/**
 根据图片名称,生成给定尺寸的圆角图像
 
 @param imgName 图像名称
 @param size 要生成的图像大小
 @param fillColor 填充颜色
 @param completion 完成回调返回新的图像
 */
+ (void )lc_circleImageWithNamed:(NSString *)imgName
                            size:(CGSize)size
                       fillColor:(UIColor *)fillColor
                      completion:(void (^)(UIImage *circleImg))completion;

实现:

#pragma mark - <创建拉伸图片>
+ (void)lc_circleImageWithNamed:(NSString *)imgName
                           size:(CGSize)size
                      fillColor:(UIColor *)fillColor
                     completion:(void (^)(UIImage *circleImg))completion {
    
    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    
    // 异步操作 执行绘图
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 1. 开启图像上下文
        UIGraphicsBeginImageContextWithOptions(size, YES, 0);
        
        // 2. 设置颜色填充
        [fillColor setFill]; // 设置颜色
        UIRectFill(rect); // 填充
        
        // 3. 圆角裁切
        [path addClip]; // 裁切
        
        // 4. 绘图
        UIImage *image = [UIImage imageNamed:imgName];
        [image drawInRect:rect];
        
        // 5. 取到结果
        UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
        
        // 6. 关闭上下文
        UIGraphicsEndImageContext();
        
        // 7. 主线程回调
        dispatch_async(dispatch_get_main_queue(), ^{
            
            //执行回调
            completion(newImg);
        });
    });
}

调用:

// 圆角图像
UIImageView *circleImgView = [[UIImageView alloc] initWithFrame:(CGRectMake(20, 100, 150, 150))];
[self.view addSubview:circleImgView];
[UIImage lc_circleImageWithNamed:@"111.jpg" size:circleImgView.bounds.size fillColor:self.view.backgroundColor completion:^(UIImage *circleImg) {
	circleImgView.image = circleImg;
}];

# 2. 贝塞尔曲线

使用贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的:

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                       cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];

# 二. 拉伸图像的性能优化

  • 正常创建图像时 如果图像小于视图大小会拉伸创建
  • 如果每次都拉伸然后创建(在 GPU 和 CPU 之间切换) 会影响性能

# 1. 解决方式:

使用绘图先创建一个和视图一样大的图像, 使用新生成的图像, 避免了每次都拉伸。

声明:

/**
 根图像名称, 生成给定尺寸的拉伸图像
 
 @param imgName 图像名称
 @param size 图像尺寸
 @param completion 完成回调返回新的图像
 */
+ (void )lc_drawImageWithNamed:(NSString *)imgName
                          size:(CGSize)size
                    completion:(void (^)(UIImage *drawImg))completion;

实现:

#pragma mark - <创建圆角图片>
+(void)lc_drawImageWithNamed:(NSString *)imgName
                        size:(CGSize)size
                  completion:(void (^)(UIImage *))completion {
    
    
    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    
    // 异步操作 执行绘图
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 1. 开启图像上下文
        UIGraphicsBeginImageContextWithOptions(size, YES, 0);
        
        // 2. 绘图
        UIImage *image = [UIImage imageNamed:imgName];
        [image drawInRect:rect];
        
        // 3. 取到结果
        UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
        
        // 4. 关闭上下文
        UIGraphicsEndImageContext();
        
        // 5. 主线程回调
        dispatch_async(dispatch_get_main_queue(), ^{
            
            //执行回调
            completion(newImg);
        });
    });
}

调用:


// 拉伸图像
UIImageView *drawImgView = [[UIImageView alloc] initWithFrame:(CGRectMake(200, 100, 150, 150))];
[self.view addSubview:drawImgView];
[UIImage lc_drawImageWithNamed:@"111.jpg" size:drawImgView.bounds.size completion:^(UIImage *drawImg) {
	drawImgView.image = drawImg;
}];

# 三. 加载图像的方式

# 1. imageNamed:

//方法1
UIImage *image1 = [UIImage imageNamed:@"name.png"];

imageNamed 的优点在于可以缓存已经加载的图片。

  • 这种方法会首先在系统缓存中根据指定的名字寻找图片,如果找到了就返回。
  • 如果没有在缓存中找到图片,该方法会从指定的文件中加载图片数据,并将其缓存起来,然后再把结果返回。
  • 对于同一个图像,系统只会把它Cache到内存一次,这对于图像的重复利用是非常有优势的。

例如:你需要在 一个TableView里重复加载同样一个图标,那么用imageNamed加载图像,系统会把那个图标Cache到内存,在Table里每次利用那个图 像的时候,只会把图片指针指向同一块内存。这种情况使用imageNamed加载图像就会变得非常有效。

# 2. data:

//方法2
UIImage *image2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image@2x" ofType:.png]];

//方法3
NSData *imageData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image@2x" ofType:png]];
UIImage *image3 = [UIImage imageWithData:imageData]

这两种方法只是简单的加载图片,并不会将图片缓存起来,图像会被系统以数据方式加载到程序。

注意: 该方法下,不能将用到的图片放在“Assets.xcassets”中管理,要放在目录文件夹下,这样才可以使用。

# 3. 总结:

  • 一些不常用的大图片,可以使用 方式二或者方式三,不必要缓存起来,可以解决内存飙升。
  • 对于常用到的小图(如app里的各种小图标)使用方法一,可以节省出每次都从磁盘加载图片的时间,这样能更好的响应用户的操作。