筆者之前做了一個類似圖片瀏覽器的應(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后寫入到沙盒,這樣初步看好像是沒什么問題览芳,行斜姥,我們運行一下。
![Uploading 屏幕快照 2017-05-17 05.59.37_446843.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