iOS 批量圖片寫入沙盒 內(nèi)存暴漲的問題

筆者之前做了一個類似圖片瀏覽器的應(yīng)用帆离,有一個功能就是打開系統(tǒng)相冊渗蟹,批量選擇圖片 讀入內(nèi)存并保存在沙盒中贮尉,下次直接讀取沙盒獲取這些圖片并展示拌滋。

在批量選擇圖片這里遇到了問題,在寫入沙盒的時候猜谚,內(nèi)存一直暴漲败砂,直至APP閃退(5s手機測試)!測試程序

首先看下代碼


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
  [wkSelf clearImgFile]; // 刪除圖片
  [wkSelf saveImgsToLocalFilePath:aryImgs]; // 批量保存
});
/** 保存圖片到沙盒中 */
- (void) saveImgsToLocalFilePath:(NSArray<UIImage*>*)aryImgs
{
    // 首先先刪除原先的圖片魏铅,然后才保存新選擇的圖片
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *strFileDir = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"/ImgsFileDirectory/"];
    if([fileManager fileExistsAtPath:strFileDir] != YES){
        BOOL bIsCreate = [fileManager createDirectoryAtPath:strFileDir withIntermediateDirectories:YES attributes:nil error:nil];
        if(bIsCreate != YES){
            return;
        }
    }
    for(int i=0; i<aryImgs.count; i+=1){
        @autoreleasepool {
            NSString *strPath = [NSString stringWithFormat:@"%@/%i.jpg", strFileDir, i];
            NSLog(@"%@", strPath);
            UIImage *img = aryImgs[i];
            NSData *data = UIImageJPEGRepresentation(img, 0.2);
            img = nil;
            [data writeToFile:strPath atomically:NO];
            data  = nil;
        }
    }
}

我是用一個for循環(huán)昌犹,把圖片對象 UIImage 轉(zhuǎn)換成NSData后寫入到沙盒,這樣初步看好像是沒什么問題览芳,行斜姥,我們運行一下。

WechatIMG11.jpeg
屏幕快照 2017-05-17 05.59.37.png

![Uploading 屏幕快照 2017-05-17 05.59.37_446843.png . . .]


屏幕快照 2017-05-17 05.59.49.png

直接內(nèi)存警告閃退2拙埂V簟!
后面通過一步一步查找悟泵,發(fā)現(xiàn)內(nèi)存暴漲的問題出現(xiàn)在這句代碼

 NSData *data = UIImageJPEGRepresentation(img, 0.2);

這句話沒問題呀杈笔,官方文檔也沒說明有什么需要注意的,把data置nil了也沒有釋放內(nèi)存糕非,而且在執(zhí)行UIImageJPEGRepresentation的代碼塊結(jié)束之后數(shù)據(jù)依然保存在內(nèi)存中蒙具,沒有被復用敦第。(筆者水平有限,暫時還想不明白為什么一直沒釋放這塊內(nèi)存)
筆者猜測是UIImageJPEGRepresentation里面沒有釋放轉(zhuǎn)換后的圖片數(shù)據(jù)店量,改成UIImagePNGRepresentation也一樣芜果,即使使用了AutoReleaseTool也沒用!
后面試了好多方法融师,比如分開多個線程存儲圖片右钾,不過還是沒能解決UIImageJPEGRepresentation占用內(nèi)存的問題。
只能想其他辦法解決問題了旱爆,怎么辦舀射,壓縮圖片唄!;陈住脆烟!
壓縮圖片的方式這里是直接壓縮大小

/* CPublic.m */
/** 根據(jù)newSize大小裁剪圖片,避免圖片過大占用內(nèi)存 */
+ (UIImage*) getNewImg:(UIImage*)oldImg withNewSize:(CGSize)newSize
{
    UIImage *newImg = nil;
    if(oldImg == nil) { return newImg; }
   
    NSLog(@"%i * %i  -- to -- %i * %i", (int)oldImg.size.width, (int)oldImg.size.height, (int)newSize.width, (int)newSize.height);
    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0); // 避免圖片模糊
    [oldImg drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    oldImg = nil;
    newImg = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImg;
}

OK房待,來邢羔,我們用一個數(shù)組 ,保存40個圖片桑孩,而且每個圖片原始像素為4000*4000拜鹤,開啟子線程進行壓縮圖片實現(xiàn),代碼示例如下:

NSMutableArray<UIImage*> *muAryNewImgs = [NSMutableArray new];
for(UIImage *oldImg in aryOldImgs){
     UIImage *imgNew = [self handleImgSizeWithImgObj:oldImg];
     [muAryNewImgs addObject:imgNew];
     imgNew = nil;
}
- (UIImage*) handleImgSizeWithImgObj:(UIImage*)oldImg
{
    CGSize sizeImg = mAnimationView.mImgSize;
    CGSize sizeNew;
    if(oldImg.size.height > oldImg.size.width){
        float value = oldImg.size.height / (float)sizeImg.height;
        sizeNew.height = (int)(sizeImg.height);
        sizeNew.width = (int)(oldImg.size.width /value);
    } else {
        float value = oldImg.size.width / (float)sizeImg.width;
        sizeNew.height = (int)(oldImg.size.height/value);
        sizeNew.width = (int)(sizeImg.width);
    }
    NSLog(@" [CPublic getNewImg:oldImg withNewSize:sizeNew] - start");
    UIImage *imgNew = [CPublic getNewImg:oldImg withNewSize:sizeNew];
    NSLog(@" [CPublic getNewImg:oldImg withNewSize:sizeNew] - end");
    oldImg = nil;
    return imgNew;
}

OK流椒,這樣的話敏簿,貌似是沒有問題了,網(wǎng)上絕大多數(shù)的方案都是這樣壓縮圖片宣虾,而且也沒說明會有什么問題惯裕,我們來試試一下。
同樣內(nèi)存暴漲绣硝,也而且會暴漲到內(nèi)存警告閃退r呤啤!域那!
為什么咙边,圖片已經(jīng)壓縮了呀?
如果你開始親自一步步調(diào)試猜煮,就可以輕松找到內(nèi)存暴漲的原因次员,就這句代碼:

[oldImg drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];

前面我們說到,每張圖片的原始像素大小都是4000x4000王带,那么圖片在內(nèi)存中的大小大概是4096 x 4096 x 2Byte ≈ 32M淑蔚,即,你每次調(diào)用 drawInRect 方法愕撰,相當于把32M的圖片繪制在一塊內(nèi)存塊上刹衫!
既然找到了內(nèi)存暴漲的原因醋寝,那就想辦法解決啦,問題在于带迟,即使而且調(diào)用UIGraphicsEndImageContext 關(guān)閉畫布音羞,系統(tǒng)好像也不會把這塊內(nèi)存(32M)釋放掉(這也許是iOS API本身的問題吧),
后面通過查找仓犬,筆者注意到嗅绰,主要oldImg這個對象指向的圖片內(nèi)存如果沒有釋放掉,那么drawInRect占用的圖片內(nèi)存會一直存在搀继,而且還找不到方法可以釋放這塊內(nèi)存窘面;而如果把oldImg給釋放了,那么這塊內(nèi)存也會隨之釋放叽躯。

這樣的話财边,得出這樣的解決方法:在每次壓縮繪制結(jié)束之后及時把oldImg給釋放掉。

__block NSMutableArray<UIImage*> *muAryBlock = [NSMutableArray arrayWithArray:images]; // images是系統(tǒng)相冊返回的圖片數(shù)組
images = nil; // 減少圖片的引用計數(shù)
dispatch_group_async(group, queue, ^{
    aryImgsAfterHandleSize = [wkSelf handleImgSize:muAryBlock];
    muAryBlock = nil;
});
/** 處理圖片 */
- (NSArray<UIImage*>*) handleImgSize:(NSMutableArray<UIImage*>*) muAryOldImgs
{
    NSMutableArray<UIImage*> *muAryNewImgs = [NSMutableArray new];
    if(muAryOldImgs != nil){
        while(muAryOldImgs.count > 0){
            @autoreleasepool{
                UIImage *oldImg = muAryOldImgs[0];
                NSLog(@"handleImgSizeWithImgObj - start");
                UIImage *imgNew = [self handleImgSizeWithImgObj:oldImg];
                [muAryNewImgs addObject:imgNew];
                NSLog(@"handleImgSizeWithImgObj - end");
                imgNew = nil;
                [muAryOldImgs removeObject:oldImg]; // 即使處理過的釋放圖片
                oldImg = nil;
            }
        }
    }
    return muAryNewImgs;
}

再次運行調(diào)試点骑,歐拉酣难,發(fā)現(xiàn)每次循環(huán)處理圖片之后,drawInRext占用的內(nèi)存都會被釋放掉黑滴,也不會出現(xiàn)內(nèi)存警告的問題了鲸鹦。(真又出現(xiàn)警告了,就讓線程等一會再繼續(xù)執(zhí)行也可以呀)


至于為什么在循環(huán)中要把drawInRext放在自動釋放池里面的問題跷跪,可以繼續(xù)看我的這篇文章:http://www.reibang.com/p/ba45f5539e4e

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馋嗜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吵瞻,更是在濱河造成了極大的恐慌葛菇,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橡羞,死亡現(xiàn)場離奇詭異眯停,居然都是意外死亡,警方通過查閱死者的電腦和手機卿泽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門莺债,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人签夭,你說我怎么就攤上這事齐邦。” “怎么了第租?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵措拇,是天一觀的道長。 經(jīng)常有香客問我慎宾,道長丐吓,這世上最難降的妖魔是什么浅悉? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮券犁,結(jié)果婚禮上术健,老公的妹妹穿的比我還像新娘。我一直安慰自己粘衬,他們只是感情好苛坚,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著色难,像睡著了一般泼舱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枷莉,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天娇昙,我揣著相機與錄音,去河邊找鬼笤妙。 笑死冒掌,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蹲盘。 我是一名探鬼主播股毫,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼召衔!你這毒婦竟也來了铃诬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤苍凛,失蹤者是張志新(化名)和其女友劉穎趣席,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體醇蝴,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡宣肚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡钥勋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笙瑟,到底是詐尸還是另有隱情,我是刑警寧澤腥沽,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布逮走,位于F島的核電站鸠蚪,受9級特大地震影響今阳,放射性物質(zhì)發(fā)生泄漏师溅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一盾舌、第九天 我趴在偏房一處隱蔽的房頂上張望墓臭。 院中可真熱鬧,春花似錦妖谴、人聲如沸窿锉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗡载。三九已至,卻和暖如春仍稀,著一層夾襖步出監(jiān)牢的瞬間洼滚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工技潘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遥巴,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓享幽,卻偏偏與公主長得像铲掐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子值桩,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

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