XZ_iOS之异步裁切绘制圆角图形

本文介绍iOS中使用异步裁切绘制圆角图的方法,避免因频繁重绘导致的性能问题。并提供Objective-C和Swift两种语言的具体实现。
之所以要异步裁切绘制圆角图形,是因为系统的 ‘layer.cornerRadius’方法十分的耗性能

tableView的性能优化
不要动态的修改’cornerRadius’之类图层渲染相关的属性,因为CornerRadius 耗性能,而tableView是从缓冲池中获取的可重用的cell,然后设置模型,如果在tableView的cell中设置圆角图层属性,在界面上会不断的重绘,例如,给cell中的图片设置圆角要使用贝塞尔曲线进行裁切绘制;
使用颜色不能带alpha透明度,使用透明度颜色会严重影响性能!

iOS中画图跟油画一样,前面的图会把后面的部分遮挡住,这是不透明的颜色,如果我们设置透明颜色,透明度一改,会把后面的图片透出来,比如,我们先画一个灰色的矩形,然后再在灰色的上面盖一层黄色的圈,我们改了黄色图片的透明度,会把后面的灰色背景透出来,透出来之后,在整个屏幕绘制的过程中,要先把灰色的部分画出来,然后要计算它跟这个圆的叠加的面积有多大,然后再根据黄色的颜色的透明度来计算灰色的颜色叠加了黄色的颜色,带多少透明度的产生的一个值,性能会降低。


在开发过程中,我们可以使用模拟器的混合模式来判断程序执行的性能:
点击模拟器 - Debug - Color Blended Layers  打开混合模式,在这个模式下,模拟器上如果出现红色覆盖,除了UILabel之外,我们可以不管,其他的能够处理掉的都要想办法进行处理;
因为UILabel的内部封装的东西,性能非常的不好,但是不用它又不行。UILabel的性能是非常糟糕的。模拟器中的混合模式,出现红色覆盖,说明是做过透明处理的!

如下图所以,我们使用下列代码设置圆角的话,会降低性能,所以,我们使用异步裁切来绘制圆角图形
imageV.layer.masksToBounds = YES;
    imageV.layer.cornerRadius = imageV.bounds.size.width * 0.5;

代码实现
UIImage+XZExtension.h
#import<UIKit/UIKit.h>

@interface UIImage (XZExtension)

/**
 *根据当前图像,和指定的尺寸,生成圆角图像并且返回
 * @return圆角图像
 */
- (void)xz_cornerImageWithSize:(CGSize)size fillColor:(UIColor *)fillColor completed:(void (^)(UIImage *image))completed;

@end

UIImage+XZExtension.m
#import"UIImage+XZExtension.h"

@implementation UIImage (XZExtension)

/**
 *根据当前图像,和指定的尺寸,生成圆角图像并且返回
 * @return圆角图像
 */
- (void)xz_cornerImageWithSize:(CGSize)size fillColor:(UIColor *)fillColor completed:(void (^)(UIImage *image))completed {
   
   dispatch_async(dispatch_get_global_queue(0,0), ^{
       //看程序执行耗时
       NSTimeInterval start =CACurrentMediaTime();
       
       // 1.利用绘图,建立上下文
       UIGraphicsBeginImageContextWithOptions(size,YES,0);
       
       CGRect rect =CGRectMake(0,0, size.width, size.height);
       
       // 2.设置被裁切的部分的填充颜色
        [fillColorsetFill];
       UIRectFill(rect);
       
       // 3.利用贝塞尔路径实现裁切效果
       UIBezierPath *path = [UIBezierPathbezierPathWithOvalInRect:rect];
       
        [pathaddClip];
       
       // 4.绘制图像
        [selfdrawInRect:rect];
       
       // 5.取得结果
       UIImage *result =UIGraphicsGetImageFromCurrentImageContext();
       
       // 6.关闭上下文
       UIGraphicsEndImageContext();
       
       NSLog(@"耗时:%f",CACurrentMediaTime() - start);
       
       // 7.使用回调,返回结果
       dispatch_async(dispatch_get_main_queue(), ^{
           if (completed) {
                completed(result);
            }
        });
       
    });

}


重点总结:
/**
 // 1.设置被裁切的部分的填充颜色
 [fillColor setFill];
 UIRectFill(rect);
 如果不设置的话,被裁切的部分是黑色的;
 
 // 2.使用CACurrentMediaTime()方法查看程序的耗时,打印结果 ==== 耗时:0.013411
 百分之一秒,程序执行东西的时候应该按纳秒来计算,CPU执行程序应该按纳秒来计算,干一个活就占了百分之一秒,一秒能干好多好多程序呢,所以还是挺浪费时间的,
 在主线程中挺浪费时间的,所以,把耗时的操作放在子线程中。
 
 // 3.如何回调?blockiOS开发中,block最多的用途就是在异步执行完成之后,通过参数回调通知调用方法结果!
 */

Swift 代码实现
/// 将给定的图像进行拉伸,并且返回'新'的图像
    ///
    /// - Parameters:
    ///   - size:      指定大小
    ///   - fillColor: 填充颜色
    /// - Returns:     UIImage
    func xz_cornerImage(size: CGSize?, fillColor: UIColor = .white, lineColor: UIColor = .lightGray) -> UIImage? {
        // 看程序执行耗时
        let start: TimeInterval = CACurrentMediaTime()
        
        var size = size
        
        if size == nil || size?.width == 0 {
            size = CGSize(width: 34, height: 34)
        }
            
        let rect = CGRect(origin: CGPoint(), size: size!)
            
        // 1.利用绘图,建立上下文 - 内存中开辟一个地址,跟屏幕无关!
        /**
        参数:
            1> size: 绘图的尺寸
            2> 不透明:false / true
            3> scale:屏幕分辨率,生成的图片默认使用 1.0 的分辨率,图像质量不好;可以指定 0 ,会选择当前设备的屏幕分辨率
        */
        UIGraphicsBeginImageContextWithOptions(rect.size, true, 0)
            
        // 2.设置被裁切的部分的填充颜色
        fillColor.setFill()
        UIRectFill(rect)
        
        // 3.利用 贝塞尔路径 实现 裁切 效果
        // 1>实例化一个圆形的路径
        let path = UIBezierPath.init(ovalIn: rect)
        // 2>进行路径裁切 - 后续的绘图,都会出现在圆形路径内部,外部的全部干掉
        path.addClip()
        
        // 4.绘图 drawInRect 就是在指定区域内拉伸屏幕
        draw(in: rect)
    
        // 5.绘制内切的圆形
        let ovalPath = UIBezierPath.init(ovalIn: rect)
        ovalPath.lineWidth = 2
        lineColor.setStroke()
        ovalPath.stroke()
//        UIColor.darkGray.setStroke()
//        path.lineWidth = 2
//        path.stroke()
        
        // 6.取得结果
        let result = UIGraphicsGetImageFromCurrentImageContext()
        
        // 7.关闭上下文
        UIGraphicsEndImageContext()
        
        // 8.返回结果
        return result
        // FIXME: 查看是否耗时
        print("耗时 - \(CACurrentMediaTime() - start)")
        
        }





`xz_dec_run` 是 XZ 解压缩库中的一个关键函数,用于执行实际的解压缩操作。在某些情况下,调用 `xz_dec_run` 时可能会遇到错误,例如返回 `XZ_DATA_ERROR` 或 `XZ_BUF_ERROR`,这通常表示输入数据损坏、缓冲区不足或解压缩流程配置不当。这类问题在嵌入式系统中尤为关键,尤其是在使用 MTD(Memory Technology Device)设备时,如 NAND 或 NOR Flash 存储器,数据完整性问题可能导致解压缩失败 [^3]。 ### 常见问题与解决方法 #### 1. **输入数据损坏或不完整** 如果输入流在传输或存储过程中被损坏,`xz_dec_run` 会返回 `XZ_DATA_ERROR`。这通常发生在固件更新失败、文件系统损坏或存储介质错误的情况下。 **解决方法:** - 确保输入数据完整无损,可以通过校验和(如 CRC32)验证数据完整性。 - 在嵌入式系统中,启用 MTD 层的 ECC 校正功能,确保数据读取过程中能够自动修复位翻转问题 [^3]。 #### 2. **缓冲区大小不足** `xz_dec_run` 需要足够的输入和输出缓冲区来处理数据。如果缓冲区太小,可能导致 `XZ_BUF_ERROR`。 **解决方法:** - 确保输入缓冲区 (`in` 指针) 和输出缓冲区 (`out` 指针) 足够大,建议使用 4KB 或更大的缓冲区。 - 动态分配缓冲区以适应不同大小的数据块。 #### 3. **解压缩器未正确初始化** 调用 `xz_dec_run` 前必须正确调用 `xz_dec_init()` 初始化解压缩器。如果跳过此步骤或初始化失败,可能导致运行时错误。 **解决方法:** - 在调用 `xz_dec_run` 之前,确保 `xz_dec_init()` 成功执行。 - 检查返回值,如果初始化失败,应释放资源并返回错误码。 #### 4. **流式处理不当** `xz_dec_run` 支持流式解压缩,但如果未正确处理输入/输出指针和缓冲区状态,可能导致解压缩不完整或死循环。 **解决方法:** - 正确管理 `in_pos` 和 `out_pos` 指针,确保每次调用 `xz_dec_run` 时更新缓冲区偏移量。 - 在读取完所有输入数据后,检查是否所有输出数据都已处理完毕。 #### 5. **内存分配失败** `xz_dec_run` 可能依赖动态内存分配,如果内存不足,可能导致解压缩失败。 **解决方法:** - 使用自定义内存分配器(如 `ISzAlloc` 接口)来管理内存分配,确保在资源受限的环境中也能正常工作 [^2]。 - 在嵌入式系统中预留足够的内存用于解压缩操作。 ### 示例代码:使用 xz_dec_run 解压缩 ```c #include <stdio.h> #include <stdlib.h> #include "xz.h" #define BUF_SIZE 4096 int decompress_xz(const char *in_file, const char *out_file) { FILE *fin = fopen(in_file, "rb"); FILE *fout = fopen(out_file, "wb"); if (!fin || !fout) { return -1; } struct xz_dec *state; struct xz_buf buf; uint8_t in[BUF_SIZE]; uint8_t out[BUF_SIZE]; state = xz_dec_init(XZ_DYNALLOC, 0); if (!state) { fclose(fin); fclose(fout); return -1; } buf.in = in; buf.in_size = 0; buf.out = out; buf.out_size = BUF_SIZE; while (1) { size_t in_read = fread((void *)buf.in + buf.in_pos, 1, BUF_SIZE - buf.in_pos, fin); buf.in_size = buf.in_pos + in_read; enum xz_ret ret = xz_dec_run(state, &buf); fwrite(out, 1, buf.out_pos, fout); buf.out_pos = 0; if (ret == XZ_STREAM_END) { break; } else if (ret != XZ_OK) { xz_dec_end(state); fclose(fin); fclose(fout); return -1; } if (in_read == 0 && buf.in_pos == buf.in_size) { break; } } xz_dec_end(state); fclose(fin); fclose(fout); return 0; } ``` ### 调试建议 - 使用日志记录每次调用 `xz_dec_run` 的返回值和缓冲区状态,有助于定位问题。 - 在嵌入式设备中,可以尝试使用 `xz` 工具链在主机端验证压缩文件的完整性。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值