基於如今iOS11新生成的圖片都是HEIF
,該圖片使用UIImage(named: name)
已不在那麼優雅,圖片大小爲1.8m大小的,讀進手機內存,直接飆升了45M,這是咱們不想看到的結果,一個頁面有多個這樣子的圖的話,恐怕就是災難了。git
既然原圖不能讀入,那麼如何能夠用更少的內存和CPU來解決呢?github
這就要先了解該圖片的編碼了。swift
帶有元數據的HEIF的另外一種形式。HEIC文件包含一個或多個以「高效圖像格式」(HEIF)保存的圖像,該格式一般用於在移動設備上存儲照片。它可能包含單個圖像或圖像序列以及描述每一個圖像的元數據。最常使用文件擴展名「 .heic」,但HEIC文件也可能顯示爲.HEIF文件緩存
heic
和heif
是廣色域圖片的格式,廣色域比sRGB
表示範圍大25%,在廣色域設備中能顯示更廣的色彩,sRGB 8bit/dept
,廣色域達到16bit/dept
。廣色域只是在硬件支持的狀況下才能顯示的。 其實就是蘋果搞的一個更高效體積更小效率更高的壓縮方式。bash
加載image
,只是把文件信息加載到內存中,下一步就是解碼。在代碼中體現就是session
let image = UIImage(contentsOfFile: url.path)
或 加載圖片到內存 會常駐內存
let image = UIImage(named: name)!
複製代碼
實際上是發生在添加到要顯示的view上面纔會解碼app
let imageV = UIImageView.init(image: image)
imageV.frame = CGRect(x: 50, y: (250 * i) + 100, width: 200, height: 200)
self.view.addSubview(imageV)
複製代碼
最後一行不寫,則不會解碼。iphone
當view
顯示出來則是渲染。過程是解碼的data buffer
複製到frame buffer
,硬件從幀緩衝區讀取數據顯示到屏幕上。ide
self.view.addSubview(imageV)
複製代碼
一部分圖片加載到內存,在解碼過程當中出現了內存暴漲問題,今天探究一下緣由和解決方案。post
首先有請咱們準備的素材和設備(6s 64g版本)
A:jpg
20M 12000*12000
B:jpg
2.8M 3024*4032
C:HEIC
1.8M 3024*4032
複製代碼
素材A
APP運行內存:13.8M
加載Image: 240.3M以後穩定到220M
CPU:峯值5%,隨後下降到0%
image佔內存:226.5M
複製代碼
素材B
APP運行內存:13.7M
加載Image: 31.5
CPU:峯值5%,隨後下降到0%
image佔內存:17.8M
複製代碼
素材C
APP運行內存:13.8M
加載Image: 32.3
CPU:峯值4%,隨後下降到0%
image佔內存:18.5M
複製代碼
咱們猜想是不是imageView
的大小影響內存的呢? size
改成原來的1/10結果運行內存仍是和之前同樣。
爲何呢?
內存大小不是取決於
view
的size
,而是原始文件image size。
每一個像素4字節
每一個像素8字節,使用機型iphone7 、iphone八、iphone X及之後的設備,不支持該格式的機型沒法顯示該效果。
每一個像素2字節,單一的色調和透明度,只能來顯示白色和黑色之間的色值,沒有其餘顏色。
每一個像素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峯值,那麼
使用素材A下采樣技術,使用UIKit
中的UIGraphicsImageRenderer
Memory
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
。
使用上下文繪製 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
}
複製代碼
使用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
}
複製代碼
使用濾鏡處理反而有點麻煩,在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)
}
複製代碼
使用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()
}
複製代碼
vImage
略微複雜點,平時開發過程當中能夠不用考慮了。廣告時間