iOS性能优化-在iOS中高效的加载图片

iOS性能优化-在iOS中高效的加载图片,第1张

在iOS开发中, 图片(UIImage) 是我们在开发中,占用手机 内存 比较大的对象,如果在运行过程中,内存占用过大,对 电池寿命 会造成影响,如果超过了 内存占用的最大值 ,会造成App的 crash 。这篇文章从 图片的加载 原理和 SDWebImage 的源码实现的角度来介绍图片加载。

在iOS中使用 UIImage 和 UIImageView 来记载图片,他俩遵守经典的 MVC 架构, UIImage 相当于 Model , UIImageView 相当于 View :

UIImage 负责 加载图片 , UIImageView 负责 渲染图片 。

图片的渲染流程分为 3个阶段 : 加载(Load),解码(Decoder)和渲染(Render)

在每个阶段都会有相对应的 缓冲区 : 数据缓冲区(DataBuffer),图像缓冲区(imageBuffer)和帧缓冲区(framebuffer) 。

我们以加载一个图片的尺寸为: 2048 px * 1536 px ,在磁盘上的大小为: 590kb 的图片为例,来分析前两个阶段的缓冲区。

DataBuffer 只是一种包含 一系列字节 的缓冲区。通常以某些 元数据 开头, 元数据 描述了存储在数据缓冲区中的图像大小,包含图形数据本身, 图像数据以某种形式编码 如 JPEG压缩或PNG,这意味着, 该字节并不直接描述图像中像素的任何内容 。此时的 DataBuffer 大小为 590kb 。

在 SDWebImage 中,图片加载完成后,在 sd_imageFormatForImageData 的方法中,是通过 DataBuffer 的 第一个字节 来判断图片的格式的。

在 图片加载 完后,需要将 Data Buffer 的 JPEG,PNG或其他编码的数据 ,转换为 每个像素 的 图像信息 ,这个过程,称为 Decoder(解码) ,将 像素信息 存放在 ImageBuffer 。

图片占用的内存大小与 图像的尺寸有关 ,与它的 文件大小无关 ,在iOS SRGB 显示格式中 (4byte空间显示一个像素) ,如果解析所有的像素,需要 2048 px * 1536 px * 4 byte/px = 10MB 的空间,此时的 ImageBuffer 的大小为 10MB 。

在 ImageBuffer 解析完后,提交给 frameBuffer 进行渲染显示。

总的来说,图片加载过程和消耗的内存如下图所示:

在 Xcode 工程中,当push新页面的时候,只加载一个图片。

加载前内存值:

加载后内存值:

大多数情况下,我们并不需要如此高精度的显示图片,占用了这么多的内存,能否减少加载图片时占用的内存值呢?

在苹果官方文档中,建议我们使用 向下采样(Downsampleing) 的技术,来加载图片,减少 ImageBuffer 的大小。

方法如下:

我们来测试一下:

加载之前时是 13M ,加载之后是 17M ,效果是很明显的,节省了大约 5M 的内存空间。

在对图片进行压缩时,我们应首选向下采样技术 。

在 SDWebIamge 中,一共有3种类型的解码器: SDImageIOCoder, SDImageGIFCoder, SDImageAPNGCoder ,根据 DataBuffer 的编码类型,使用相对应的编码器。

在 -(UIImage *)decodedImageWithData:(NSData *)data 方法中,配置解码参数,开始进行解码 *** 作。

在 + (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary )options 中,完成图像解码

在 iOS中,渲染图片格式有4种

正确的思路是: 不选择渲染格式,让渲染格式选择你 。

使用 UIGraphicsImageRender 来替换 UIGraphicsBeginImageContextWithOptions ,前者在 iOS12 以后,会自动选择渲染格式,后者默认都会选择 SRGB Format 。

此时,系统为自动选择 Alpha 8 Format 格式,内容空间占用,将会减少 75% 。

在需要绘制带有子视图的View时,不使用 draw(rect:) 方法,使用 系统的View属性 或者 添加子视图 的方式,将绘制工作交给系统来处理。

背景色直接通过 UIView.backgroundColor 设置,而非使用 draw(rect:)

我们在开发中,一般会对图片进行 子线程异步加载 ,在后台进行 解码和下采样 。在列表中,有时会加载很多图片,此时应该注意 线程爆炸 问题。

当我们要求 系统去做比CPU能够做的工作更多的工作时 就会发生这种情况,比如我们要显示 8张图片 ,但我们只有 两个CPU ,就不能一次完成所有这些工作,无法在不存在的CPU上进行并行处理, 为了避免向一个全局队列中异步的分配任务时发生死锁 , GCD 将创建新线程来捕捉我们要求它所做的工作,然后CPU将花费大量时间,在这些 线程 之间进行 切换 ,尝试在所有工作上取得我们要求 *** 作系统为我们做的 渐进式进展 ,在这些线程之间 不停切换 ,实际上是相当大的开销,现在 不是简单地将工作分派到全局异步队列之一 ,而是 创建一个串行队列 ,在预取的方法中,异步的将工作分派到该队列,它的确意味着单个图像的加载,可能要比以前晚才能开始取得进展,但CPU将花费更少的时间,在它可以做的小任务之间来回切换。

在 SDWebImage 中,解码的队列 _coderQueue.maxConcurrentOperationCount = 1 就是一个串行队列。这样就很好的解决了 多图片异步解码 时, 线程爆炸 问题。

     UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bgImage"]] 

    创建并设置默认图, 也可以

    UIImageView*imageView = [[UIImageView alloc] init]

    imageView.image= [UIImageimageNamed:@"bgImage"]

    还可以这样先设置imageview的大, 在设置图片

     UIImageView*imageView = [[UIImageView alloc] initWithFrame:(CGRectMake(0,144,SCREEN_Width,50))]

    imageView.image= [UIImageimageNamed:@"bgImage"]

    由此可看imageview的frame可以这样设置

     imageView.frame=CGRectMake(0,144,SCREEN_Width,50)

    通常我们使用的的imageview都会添加圆角边框

     imageView.layer.masksToBounds = YES

    imageView.layer.cornerRadius=25

    imageView.layer.borderColor = [UIColor blueColor].CGColor

    imageView.layer.borderWidth=1

    这个圆角和边框像view和label以及button的设置方式都是一样的 当然imageview也一样

     imageView.backgroundColor= [UIColorclearColor]图片设置背景颜色, 我通常使用clearColor  透明

     imageView.userInteractionEnabled = YES图片设置成可交互, 设置为NO则不能交互

     [self.viewaddSubview: imageView]添加视图也可叫做显示视图

    设置图片内容的布局方式 imageView.contentMode

    这个属性是用来设置图片的显示方式,如居中、居右,是否缩放等

    imageView.contentMode = UIViewContentModeScaleAspectFit

     UIViewContentMode contentMode枚举类型

        (1)  UIViewContentModeScaleToFill    默认,对图片进行拉伸处理(不是按比例),是充满bouns

        (2)  UIViewContentModeScaleAspectFit    按原图比例进行拉伸,是图片完全展示在bouns中

        (3)  UIViewContentModeScaleAspectFill    按原图比例填充,使图片展示在bouns中,可能只显示部分

        (4)  UIViewContentModeRedraw    重划边界变化(重设 - setNeedsDisplay)

        (5)  UIViewContentModeCenter    图片显示在imageview的正中间,原图大小

        (6)  UIViewContentModeTop    图片显示在imageview的上部,原图大小

        (7)  UIViewContentModeBottom    图片显示在imageview的下部,原图大小

        (8)  UIViewContentModeLeft    图片显示在imageview的左部,原图大小

        (9)  UIViewContentModeRight    图片显示在imageview的右部,原图大小

        (10)  UIViewContentModeTopLeft    图片显示在imageview的左上部,原图大小

        (11)  UIViewContentModeTopRight    图片显示在imageview的右上部,原图大小

        (12)  UIViewContentModeBottomLeft    图片显示在imageview的左下部,原图大小

        (13)  UIViewContentModeBottomRight    图片显示在imageview的右下部,原图大小

     imageView.alpha = 1.0   设置图片透明度

        NSString *path1 = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"jpg"]

        NSString *path2 = [[NSBundle mainBundle] pathForResource:@"2" ofType:@"jpg"]

        NSString *path3 = [[NSBundle mainBundle] pathForResource:@"3" ofType:@"jpg"]

        imageView.animationImages = @[[UIImage imageWithContentsOfFile:path1],[UIImage imageWithContentsOfFile:path2],[UIImage imageWithContentsOfFile:path3]]

        imageView.animationDuration = 5.0f   设置循环一次的时间

        imageView.animationRepeatCount = 0    // 设置循环次数(0为无线循环)

        [imageView startAnimating]            // 开始动画

        [imageView stopAnimating]              // 停止动画

    NSData *imageData = [NSData dataWithContentsOfFile:path]

    UIImage *image4 = [UIImage imageWithData:imageData]

    NSString *path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"jpg"]

    UIImage *image2 = [UIImage imageWithContentsOfFile:path]

    ImageView.hidden = NO    隐藏或者显示图片 YES为隐藏

    [ImageView sizeToFit]    将图片尺寸调整为与内容图片相同

    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapImageView:)] // 设置手势

  [ImageView addGestureRecognizer:singleTap] // 给图片添加手势


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/bake/7931931.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-11
下一篇2023-04-11

发表评论

登录后才能评论

评论列表(0条)

    保存