前言
對(duì)于 iOS 系統(tǒng)而言猴凹,絕大部分場景下哪類數(shù)據(jù)占內(nèi)存最多呢?當(dāng)然是圖片宰闰!需要注意的是茬贵,圖片所占內(nèi)存的大小與圖片的尺寸有關(guān),而不是圖片的文件大小移袍。
例如:有一個(gè) 590KB 的圖片解藻,分辨率是 2048px * 1536px,它實(shí)際使用的內(nèi)存不是 590KB葡盗,而是2048 * 1536 * 4 = 12 MB螟左。
至于為什么圖片占用這么大的內(nèi)存,而不是圖片的原始大忻俟弧胶背?
這就要從圖片格式來說,我們通常用的圖片格式如:png和jpeg等喘先,這些格式的圖片都是壓縮的位圖格式钳吟,不能直接渲染展示在屏幕上,所以就需要在渲染到屏幕之前窘拯,需要將圖片解壓縮红且,得到圖片的原始像素?cái)?shù)據(jù),過程如下:
Data buffer(圖片的元數(shù)據(jù)) ->Image buffer(圖片解碼后在內(nèi)存中的表示) ->Frame buffer(代表了一幀在內(nèi)存中的表示)
想學(xué)習(xí)更詳細(xì)的內(nèi)容涤姊,可以看看這篇文章談?wù)?iOS 中圖片的解壓縮
優(yōu)化方法
一暇番、對(duì)不常用的大圖片,使用imageWithContentsOfFile代替imageNamed方法思喊,避免內(nèi)存緩存壁酬。
這是個(gè)老生常談的問題了,我就簡單說下,使用UIImage的imageNamed方法的時(shí)候厨喂,為了下次加快渲染速度,會(huì)緩存圖片內(nèi)存庄呈,所以對(duì)于使用不頻繁的大圖片進(jìn)行緩存蜕煌,非常耗費(fèi)內(nèi)存,可以使用imageWithContentsOfFile方法進(jìn)行替換诬留。
二斜纪、使用ImageIO方法,對(duì)大圖片進(jìn)行縮放文兑,減少圖片解碼占用內(nèi)存大小盒刚。
這是 WWDC2018 Image and Graphics Best Practices (中文翻譯:WWDC2018 圖像最佳實(shí)踐)中推薦的方法,可以使用這個(gè)方法對(duì)圖片進(jìn)行縮放,UIImage 在設(shè)置和調(diào)整大小的時(shí)候绿贞,需要將原始圖像加壓到內(nèi)存中因块,然后對(duì)內(nèi)部坐標(biāo)空間做一系列轉(zhuǎn)換,整個(gè)過程會(huì)消耗很多資源籍铁。我們可以使用 ImageIO涡上,它可以直接讀取圖像大小和元數(shù)據(jù)信息,不會(huì)帶來額外的內(nèi)存開銷拒名。
這是官方的實(shí)例的Swift代碼:
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
//生成CGImageSourceRef 時(shí)吩愧,不需要先解碼。
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
//kCGImageSourceShouldCacheImmediately
//在創(chuàng)建Thumbnail時(shí)直接解碼增显,這樣就把解碼的時(shí)機(jī)控制在這個(gè)downsample的函數(shù)內(nèi)
let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
//生成
let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
return UIImage(cgImage: downsampledImage)
}
這是一種OC代碼實(shí)現(xiàn):
- (UIImage *)resizeScaleImage:(CGFloat)scale {
CGSize imgSize = self.size;
CGSize targetSize = CGSizeMake(imgSize.width * scale, imgSize.height * scale);
NSData *imageData = UIImageJPEGRepresentation(self, 1.0);
CFDataRef data = (__bridge CFDataRef)imageData;
CFStringRef optionKeys[1];
CFTypeRef optionValues[4];
optionKeys[0] = kCGImageSourceShouldCache;
optionValues[0] = (CFTypeRef)kCFBooleanFalse;
CFDictionaryRef sourceOption = CFDictionaryCreate(kCFAllocatorDefault, (const void **)optionKeys, (const void **)optionValues, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CGImageSourceRef imageSource = CGImageSourceCreateWithData(data, sourceOption);
CFRelease(sourceOption);
if (!imageSource) {
NSLog(@"imageSource is Null!");
return nil;
}
//獲取原圖片屬性
int imageSize = (int)MAX(targetSize.height, targetSize.width);
CFStringRef keys[5];
CFTypeRef values[5];
//創(chuàng)建縮略圖等比縮放大小雁佳,會(huì)根據(jù)長寬值比較大的作為imageSize進(jìn)行縮放
keys[0] = kCGImageSourceThumbnailMaxPixelSize;
CFNumberRef thumbnailSize = CFNumberCreate(NULL, kCFNumberIntType, &imageSize);
values[0] = (CFTypeRef)thumbnailSize;
keys[1] = kCGImageSourceCreateThumbnailFromImageAlways;
values[1] = (CFTypeRef)kCFBooleanTrue;
keys[2] = kCGImageSourceCreateThumbnailWithTransform;
values[2] = (CFTypeRef)kCFBooleanTrue;
keys[3] = kCGImageSourceCreateThumbnailFromImageIfAbsent;
values[3] = (CFTypeRef)kCFBooleanTrue;
keys[4] = kCGImageSourceShouldCacheImmediately;
values[4] = (CFTypeRef)kCFBooleanTrue;
CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CGImageRef thumbnailImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options);
UIImage *resultImg = [UIImage imageWithCGImage:thumbnailImage];
CFRelease(thumbnailSize);
CFRelease(options);
CFRelease(imageSource);
CFRelease(thumbnailImage);
return resultImg;
}
如果你還想學(xué)習(xí)更多的ImageIO的方法和參數(shù)使用,可以參考這篇文章iOS中ImageIO框架詳解與應(yīng)用分析
經(jīng)過此預(yù)處理過程后同云,內(nèi)存占用的前后對(duì)比如下:
如果你想自己測(cè)試一下內(nèi)存占用效果糖权,可以使用instruments 中的VMTracker進(jìn)行測(cè)試,具體測(cè)試方法梢杭,可以看下這篇文章iOS中的圖片使用方式温兼、內(nèi)存對(duì)比和最佳實(shí)踐
三、用 UIGraphicsImageRenderer 代替 UIGraphicsBeginImageContextWithOptions武契,
這個(gè)方法是WWDC 2018:iOS 內(nèi)存深入研究推薦的方法募判,
使用 UIGraphicsBeginImageContextWithOptions 生成的圖片,每個(gè)像素需要 4 個(gè)字節(jié)表示咒唆。建議使用 UIGraphicsImageRenderer届垫,這個(gè)方法是從 iOS 10 引入,在 iOS 12 上會(huì)自動(dòng)選擇最佳的圖像格式全释,可以減少很多內(nèi)存装处。系統(tǒng)可以根據(jù)圖片分辨率選擇創(chuàng)建解碼圖片的格式,如選用SRGB format 格式,每個(gè)像素占用 4 字節(jié)妄迁,而Alpha 8 format寝蹈,每像素只占用 1 字節(jié),可以減少大量的解碼內(nèi)存占用登淘。
四箫老、SDWebImage配置優(yōu)化,減小CG-raster-data內(nèi)存占用
在使用SDWebImage的時(shí)候黔州,會(huì)默認(rèn)保存圖片解碼后的內(nèi)存耍鬓,以便提高頁面的渲染速度,但是這會(huì)導(dǎo)致內(nèi)存的急速增加流妻,所以可以在不影響體驗(yàn)的情況下牲蜀,選擇機(jī)型和系統(tǒng),進(jìn)行優(yōu)化绅这,避免大量的內(nèi)存占用涣达,引起OOM問題。關(guān)閉解碼內(nèi)存緩存的方法如下:
[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
參考資料:
iOS Memory Deep Dive
iOS 處理圖片的一些小 Tip
談?wù)?iOS 中圖片的解壓縮
WWDC2018 Image and Graphics Best Practices
WWDC2018 圖像最佳實(shí)踐
iOS中的圖片使用方式证薇、內(nèi)存對(duì)比和最佳實(shí)踐
iOS 圖片處理內(nèi)存優(yōu)化ImageIO
iOS中ImageIO框架詳解與應(yīng)用分析
WWDC 2018:iOS 內(nèi)存深入研究