基于现在iOS11新生成的图片都是HEIF,该图片使用UIImage(named: name)已不在那么优雅,图片大小为1.8m大小的,读进手机内存,直接飙升了45M,这是我们不想看到的结果,一个页面有多个这样子的图的话,恐怕就是灾难了。

既然原图不能读入,那么如何可以用更少的内存和CPU来解决呢?

这就要先了解该图片的编码了。

HEIC HEIF带有元数据的HEIF的另一种形式。HEIC文件包含一个或多个以“高效图像格式”(HEIF)保存的图像,该格式通常用于在移动设备上存储照片。它可能包含单个图像或图像序列以及描述每个图像的元数据。最常使用文件扩展名“ .heic”,但HEIC文件也可能显示为.HEIF文件

heic和heif是广色域图片的格式,广色域比sRGB表示范围大25%,在广色域设备中能显示更广的色彩,sRGB 8bit/dept,广色域达到16bit/dept。广色域只是在硬件支持的情况下才能显示的。 其实就是苹果搞的一个更高效体积更小效率更高的压缩方式。

加载

加载image,只是把文件信息加载到内存中,下一步就是解码。在代码中体现就是let image = UIImage(contentsOfFile: url.path)

或 加载图片到内存 会常驻内存

let image = UIImage(named: name)!

解码

其实是发生在添加到要显示的view上面才会解码let imageV = UIImageView.init(image: image)

imageV.frame = CGRect(x: 50, y: (250 * i) + 100, width: 200, height: 200)

self.view.addSubview(imageV)

最后一行不写,则不会解码。

渲染

当view显示出来则是渲染。过程是解码的data buffer 复制到frame buffer,硬件从帧缓冲区读取数据显示到屏幕上。self.view.addSubview(imageV)

内存暴涨原因

一部分图片加载到内存,在解码过程中出现了内存暴涨问题,今天探究一下原因和解决方案。

首先有请我们准备的素材和设备(6s 64g版本)A:jpg

20M 12000*12000

B:jpg

2.8M 3024*4032

C:HEIC

1.8M 3024*4032

素材AAPP运行内存:13.8M

加载Image: 240.3M之后稳定到220M

CPU:峰值5%,随后降低到0%

image占内存:226.5M

素材BAPP运行内存:13.7M

加载Image: 31.5

CPU:峰值5%,随后降低到0%

image占内存:17.8M

素材CAPP运行内存:13.8M

加载Image: 32.3

CPU:峰值4%,随后降低到0%

image占内存:18.5M

我们猜测是否是imageView的大小影响内存的呢? size改为原来的1/10结果运行内存还是和以前一样。

为什么呢?内存大小不是取决于view的size,而是原始文件image size。

渲染格式

SRGB

每个像素4字节

display p3 宽色域

每个像素8字节,使用机型iphone7 、iphone8、iphone X及以后的设备,不支持该格式的机型无法显示该效果。

亮度和透明度

每个像素2字节,单一的色调和透明度,只能来显示白色和黑色之间的色值,没有其他颜色。

Alpha 8 Format

每个像素1字节,用来表示透明度,一般用作蒙版和文字。 相比sRGB容量小了75%,详细 宽色域 容量小了87.5%

渲染图片大小计算

图片大小 = 图片格式容量 * 像素个数 当我们把大小是20*20使用Alpha 8 format渲染到20*20的view上面,和40*40的image使用p3渲染到20*20的view中,后着占用内存是前者的8倍。

使用sRGB色域进行渲染所占用的大小为imageWidth*imageHeight*4 字节

每个像素占用了4字节,每个字节8位,

使用display p3则每个通道占用16位,那么占用内存大小是imageWidth*imageHeight*8 字节

如何选择正确的图片格式不要主动选择图片格式,让格式选择你。

不要再使用UIGraphicsBeginImageContextWithOptions,该方法总是使用sRGB格式,你想节约内存是不行的,在支持p3的设备上想绘制出来p3色域的图片也是不行的。那么使用UIGraphicsImageRenderer系统可以自动为你选择格式,如果绘制image,自己再添加单色蒙版,是不需要另外单独分配内存的。if let im = imageV {

//第二次添加蒙版

im.tintColor = UIColor.black

}else{

//绘制一个红色矩形

let bounds = CGRect(x: 0, y: 0, width: width, height: height)

let renderer = UIGraphicsImageRenderer(bounds: bounds)

let image = renderer.image { (coxt) in

UIColor.red.setFill()

let path = UIBezierPath(roundedRect: bounds,

cornerRadius: 20)

path.addClip()

UIRectFill(bounds)

}

imageV = UIImageView(image: image)

imageV?.frame = bounds

self.view.addSubview(imageV!)

}

UIImage 直接读出来需要将所有UIImage的data全部解码到内存,很耗费内存和性能。为了节省内存和降低CPU使用率,可以采用下采样。

下采样

当image素材大小是1000*1000,但是在手机上显示出来只有200*200,我们其实是没必要将1000*1000的数据都解码的,只需要缩小成200*200的大小即可,这样子节省了内存和CPU,用户感官也没有任何影响。 在UIKit中使用UIGraphicsImageRenderer会有瞬间很高的内存和CPU峰值,那么

1.UIKit  UIGraphicsImageRenderer

使用素材A下采样技术,使用UIKit中的UIGraphicsImageRendererMemory

High:16.4M

normal:14.8M

CPU:

Hight:29%

normal:0%func resizedImage(at url: URL, for size: CGSize) -> UIImage? {

guard let image = UIImage(contentsOfFile: url.path) else {

return nil

}

if #available(iOS 10.0, *) {

let renderer = UIGraphicsImageRenderer(size: size)

return renderer.image { (context) in

image.draw(in: CGRect(origin: .zero, size: size))

}

}else{

UIGraphicsBeginImageContext(size)

image.draw(in: CGRect(origin: .zero, size: size))

let image = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

return image

}

}

用子线程绘制,会出现CPU略微升高,当image size大很多的时候会出现内存飙升然后慢慢恢复到normal。

2.CoreGraphics CGContext上下文绘制缩略图

使用上下文绘制 cpu 和内存变化如下,CPU和内存没有大的变动解决了该问题,也做到省电、顺滑。Memory

High:42.3M

normal:14.1M

CPU:

Hight:6%

normal:0%

func resizedImage2(at url: URL, for size: CGSize) -> UIImage?{

guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),

let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)

else{

return nil;

}

let cxt = CGContext(data: nil,

width: Int(size.width),

height: Int(size.height),

bitsPerComponent: image.bitsPerComponent,

bytesPerRow: image.bytesPerRow,

space: image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!

,

bitmapInfo: image.bitmapInfo.rawValue)

cxt?.interpolationQuality = .high

cxt?.draw(image, in: CGRect(origin: .zero, size: size))

guard let scaledImage = cxt?.makeImage() else {

return nil

}

let ima = UIImage(cgImage: scaledImage)

return ima

}

3.ImageIO 创建缩略图

使用ImageIO 中创建图像,CPU和内存记录反而更高了,内存也居高不下,时间上基本2s才将图像绘制出来。Memory

High:320M

normal:221M

CPU:

Hight:73%

normal:0%func resizedImage3(at url: URL, for size: CGSize) -> UIImage?{

let ops:[CFString:Any] = [kCGImageSourceCreateThumbnailFromImageIfAbsent:true,

kCGImageSourceCreateThumbnailWithTransform:true,

kCGImageSourceShouldCacheImmediately:true,

kCGImageSourceThumbnailMaxPixelSize:max(size.width, size.height)]

guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),

let image = CGImageSourceCreateImageAtIndex(imageSource, 0, ops as CFDictionary) else {

return nil;

}

let ima = UIImage(cgImage: image)

printImageCost(image: ima)

return ima

}

4.CoreImage 滤镜

使用滤镜处理反而有点麻烦,在iOS不是专业处理图像的APP中略微臃肿,而且性能不是很好。在重复删除添加操作,第二次出现了APP闪退问题。Memory

High:1.04G

normal:566M

CPU:

Hight:73%

normal:0%

func resizedImage4(at url: URL, for size: CGSize) -> UIImage?{

let shareContext = CIContext(options: [.useSoftwareRenderer:false])

guard let image = CIImage(contentsOf: url) else { return nil }

let fillter = CIFilter(name: "CILanczosScaleTransform")

fillter?.setValue(image, forKey: kCIInputImageKey)

fillter?.setValue(1, forKey: kCIInputScaleKey)

guard let outPutCIImage = fillter?.outputImage,let outputCGImage = shareContext.createCGImage(outPutCIImage, from: outPutCIImage.extent) else { return nil }

return UIImage(cgImage: outputCGImage)

}

5.使用 vImage 优化图片渲染

使用vImage创建图像性能略低,内存使用较多,步骤麻烦,是我们该舍弃的。在内存只有1G的手机上恐怕要crash了。Memory

High:998.7M

normal:566M

CPU:

Hight:78%

normal:0%func resizedImage5(at url: URL, for size: CGSize) -> UIImage? {

// 解码源图像

guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),

let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil),

let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],

let imageWidth = properties[kCGImagePropertyPixelWidth] as? vImagePixelCount,

let imageHeight = properties[kCGImagePropertyPixelHeight] as? vImagePixelCount

else {

return nil

}

// 定义图像格式

var format = vImage_CGImageFormat(bitsPerComponent: 8,

bitsPerPixel: 32,

colorSpace: nil,

bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),

version: 0,

decode: nil,

renderingIntent: .defaultIntent)

var error: vImage_Error

// 创建并初始化源缓冲区

var sourceBuffer = vImage_Buffer()

defer { sourceBuffer.data.deallocate() }

error = vImageBuffer_InitWithCGImage(&sourceBuffer,

&format,

nil,

image,

vImage_Flags(kvImageNoFlags))

guard error == kvImageNoError else { return nil }

// 创建并初始化目标缓冲区

var destinationBuffer = vImage_Buffer()

error = vImageBuffer_Init(&destinationBuffer,

vImagePixelCount(size.height),

vImagePixelCount(size.width),

format.bitsPerPixel,

vImage_Flags(kvImageNoFlags))

guard error == kvImageNoError else { return nil }

// 优化缩放图像

error = vImageScale_ARGB8888(&sourceBuffer,

&destinationBuffer,

nil,

vImage_Flags(kvImageHighQualityResampling))

guard error == kvImageNoError else { return nil }

// 从目标缓冲区创建一个 CGImage 对象

guard let resizedImage =

vImageCreateCGImageFromBuffer(&destinationBuffer,

&format,

nil,

nil,

vImage_Flags(kvImageNoAllocate),

&error)?.takeRetainedValue(),

error == kvImageNoError

else {

return nil

}

return UIImage(cgImage: resizedImage)

}

内存优化

图片解码后加载在内存中的数据需要在恰当的时机删除掉,在合适的时机添加上,也是保持低内存使用率的手段。

在用户拨打电话或者进入到其他APP中可以先删除掉大图片,等回来的时候再次添加也是不错的选择。# 1

NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification,

object: nil,

queue: .main)

{[weak self] (note) in

self?.unloadImage()

}

NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification,

object: nil,

queue: .main)

{[weak self] (note) in

self?.loadImage()

}

# 2

override func viewWillAppear(_ animated: Bool) {

super.viewWillAppear(animated)

self.loadImage()

}

override func viewWillDisappear(_ animated: Bool) {

super.viewWillDisappear(animated)

self.unloadImage()

}

总结基于性能综合考虑方法1是最简单最合适的

使用滤镜和vImage略微复杂点,平时开发过程中可以不用考虑了。

图片解码缓存和图片大小有关,适当的下采样是不错的选择。

参考

ios 图片加载内存尺寸_iOS图片内存优化相关推荐

  1. Android UIL图片加载缓存源码分析-内存缓存

    本篇文章我们来分析一下著名图片加载库Android-Universal-Image-Loader的图片缓存源码. 源码环境 版本:V1.9.5 GitHub链接地址:https://github.co ...

  2. vue图片加载失败使用默认图片,el-image支持懒加载,自定义占位、加载失败等

    <template><d2-container><h3>image加载失败使用默认图片</h3><img src=""alt= ...

  3. html 图片加载错误,CSS 无图片显示加载(失败)效果

    lazyload 时利用 iconfont 显示加载图片和加载失败效果 0. 效果 1. 显示加载中或者品牌图 可以是文字或者 iconfont, 并将图标显示到正中间 HTML 结构如下: .img ...

  4. htmlimg图片加载失败_js针对图片加载失败的处理方法分析

    本文实例讲述了js针对图片加载失败的处理方法.分享给大家供大家参考,具体如下: 在项目中不可避免会用到图片,尤其是列表,有时候图片会加载失败:这样就会显示一个很难看的坏图片缩略图:下面介绍两种方法,解 ...

  5. 前端页面图片加载失败显示默认图片

    方法有多种: 1.首先说我用的,看代码 //页面图片加载失败时 默认显示统一处理 document.addEventListener("error", function (e) { ...

  6. vue解决图片加载失败显示默认图片的方法

    在项目中经常会遇到图片加载失败需要显示默认图片的场景,那么如何在图片src资源加载失败显示出默认的图片呢? 方法一:onerror <img src="原来要加载的资源" o ...

  7. IOS UITableView 加载未知宽高图片的解决方案

    在开发中遇到了UITableView列表 UITableViewCell装载图片但不知Image的宽高 问题. 在解决该问题的时候,首先想到的是异步加载图片 采用第三方框架SDWebImage 实现对 ...

  8. iOS中加载Flutter中的图片

    在 Flutter 插件开发中,有时需要将 Flutter 中配置的图片资源传递到 Android 或者是 iOS原生中,传递方法如下: //一般应用在Flutter 插件开发中 //注册插件的方法 ...

  9. iOS UILabel加载html点击图片查看大图 附demo

    我们在有些时候,因为性能和加载时间的问题,需要用UILabel加载html的方式来代替webview. 大部分情况,UILabel都可以很好的展示出想要的效果,但是却不能满足点击查看大图的需求. 本解 ...

  10. iOS 动态加载LaunchScreen上的图片

    前言 hihi,勇敢的小伙伴儿们大家好,明天就要放元旦假期了,小长假三天你们准备去哪玩呢? 我就不出去玩了,买了一本新书在家攻读,如果有时间有机会的话我会写成博客跟大家一起分享. 今天分享的这个吧,恕 ...

最新文章

  1. java mvc ef_一个简单MVC5 + EF6示例分享
  2. OpenCv调用摄像头拍照代码
  3. resultmap拿不到数据_阿里巴巴国际站每日电商运营工作数据表格
  4. VS当前不会命中断点 还没有为该文档加载任何符号
  5. 第一章数据结构和算法简介
  6. 关于Windows 10 企业版 LTSC重装系统后优化项目
  7. eNSP华为路由器与交换机连接
  8. 详解函数的三种传递方式
  9. macOS免费串口工具coolTerm/Minicom/Comtool/Volt+(伏特加)/友善串口调试助手/screen/picocom
  10. cc2530 按键中断实验——按键控制LED灯的亮灭
  11. opencv存入数据库图片入门笔记
  12. 无线华为能连苹果不能连接到服务器,华为手机连苹果Mac,连不上?手把手教你...
  13. F5系统配置备份及恢复
  14. 【解决方案】智慧国土管理靠什么?EasyCVR综合性视频监控管理系统成支撑
  15. 先序序列和中序序列构造二叉树,中序序列和后序序列构造二叉树
  16. 如何运用计算机制作合同书,Word 2007 制作一份专业合同书实例WORD2007 -电脑资料...
  17. 太空射击 第07课: 添加图形
  18. 使用Python在Markdown插入图片并自动获取链接
  19. 轮训网页,并打开截图保存
  20. 关于什么时候用精灵图什么时候用字体图标

热门文章

  1. 第三方银联支付接口对接_php版银联支付接口开发简明教程
  2. php 微信公号授权登入,WordPress 微信公众号授权登录
  3. vue2.x 微信公众号授权拿取code,静默登录
  4. 老婆生成器 yyds
  5. 两台电脑之间使用ntp做时间同步的总结
  6. 计算机桌面不同步,电脑时间不同步怎么回事 电脑时间不能自动更新如何修复...
  7. Spring JdbcTemplate 多参数查询,以及like模糊查询处理方式
  8. 微信小程序 flex:1表示什么
  9. P80 例4-1 名和姓的对换问题。英国人和美国人姓名的书写形式是“名在前,姓在后”,但在有些情况下,需要把姓名写成“姓在前,名在后,中间加一个逗号”的形式。编写一个程序实现把“名在前,姓在后”的姓名
  10. JDBC - 超快速拿捏