iOS圖片內(nèi)存優(yōu)化

前言

對(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ì)比如下:

縮放前后對(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)存深入研究

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末峭判,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棕叫,更是在濱河造成了極大的恐慌林螃,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俺泣,死亡現(xiàn)場離奇詭異疗认,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)伏钠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門横漏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人熟掂,你說我怎么就攤上這事缎浇。” “怎么了赴肚?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵素跺,是天一觀的道長。 經(jīng)常有香客問我誉券,道長指厌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任踊跟,我火速辦了婚禮踩验,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己箕憾,他們只是感情好牡借,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布撞叨。 她就那樣靜靜地躺著家妆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沛膳。 梳的紋絲不亂的頭發(fā)上扁远,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音刻像,去河邊找鬼畅买。 笑死,一個(gè)胖子當(dāng)著我的面吹牛细睡,可吹牛的內(nèi)容都是我干的谷羞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼溜徙,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼湃缎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蠢壹,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤嗓违,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后图贸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹂季,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年疏日,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偿洁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沟优,死狀恐怖涕滋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挠阁,我是刑警寧澤宾肺,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站侵俗,受9級(jí)特大地震影響爱榕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坡慌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一黔酥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦跪者、人聲如沸棵帽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逗概。三九已至,卻和暖如春忘衍,著一層夾襖步出監(jiān)牢的瞬間逾苫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工枚钓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铅搓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓搀捷,卻偏偏與公主長得像星掰,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫩舟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容