天天看點

IOS 記憶體洩漏 選擇圖檔上傳記憶體洩露

IOS 記憶體洩漏

最近在做塗鴉小程式的時候,發現幾個記憶體問題。

塗鴉Demo這個程式打開後是進入到相冊選擇圖檔,接着載入一個UIScrollView,然後在UIScrollView上添加一個UIImageView,再将選擇圖檔設定為ImageView的Image。塗鴉的時候,将一個UIView加在UIImageView上,繪圖将在這個UIView上進行。

第一個問題

現在的問題是,隻要開始塗鴉,記憶體就會從暴漲,漲幅跟圖檔的大小有關,見下圖。

圖檔大小 塗鴉前記憶體 塗鴉後記憶體 記憶體漲幅
21K 4.9M 8.1M 3.2M
104K 4.5M 29.5M 25M
563K 6.9M 35.1M 28M

為什麼小圖檔塗鴉前所占記憶體更大?大概是因為圖檔較小時,對記憶體影響更大的因素可能是顔色等。104K的圖檔偏黑,而21K的圖檔偏亮。

經過不斷注釋代碼,發現記憶體暴漲的原因是,我在UIView的touchesMove函數中調用了[self setNeedsDisplay],即手指移動的每一個過程,都會讓界面重繪。那麼就可以解釋上圖為什麼記憶體的漲幅與圖檔的大小相關,因為重繪的界面與圖檔的大小相關。

目前繪圖的原理是,由于重繪的函數會清空上次的繪畫内容,是以每次重繪時需要将以前畫的軌迹重新繪一遍。舉個例子,假如畫一條線有3個點,則手指移動到第一點的時候,畫第一個點,手指移動到第二點的時候,将第一第二個點畫一下,手指移動到第三個點的時候,将第一第二第三個點都畫一下;同理,畫第二條線的時候,第一條線的每個點每時每刻都被重畫。

這種方法有個好處,就是撤銷功能很簡單,隻要将最後一條線從記錄中删除,再重繪一下,就好像撤銷了最新的那條線,其實是以前的所有線條被重畫了一次。

壞處顯而易見,耗時,耗CPU,耗記憶體。

是以現在,隻能尋找一種可以儲存上一次的繪畫内容又能完成撤銷功能的繪圖方法就應該能解決問題,待續……

第二個問題

在研究第一個問題的時候,發現第二個問題,就是從相冊中選擇圖檔,進入預覽界面後記憶體會上升(正常,因為載入了圖檔),再取消,回到相冊。此時記憶體不會變,這就有問題了,按理說預覽界面已經不存在,為什麼記憶體不會降到跟選圖前一樣呢?

用XCode的Profile的Leak工具檢查了一下,發現記憶體洩漏的原因是UIStatusBarHideAnimationParameters和UIImage。

IOS 記憶體洩漏 選擇圖檔上傳記憶體洩露

先看UIImage,檢查了一下代碼,發現一個隐藏得比較深的記憶體洩漏問題。下面是打開相冊的ViewController中的代碼,該代碼是在選擇照片後被調用。選擇照片後跳轉到預覽模式的ViewController。

IOS 記憶體洩漏 選擇圖檔上傳記憶體洩露
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    /*選擇圖檔後,獲得圖檔*/
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    /*跳轉*/
    QLMomentMainViewController *viewController = [[QLMomentMainViewController alloc] initWithNibName:nil bundle:nil];
    viewController.image = image;
    [picker dismissViewControllerAnimated:YES completion:^(void){}];
    [self.navigationController pushViewController:viewController animated:YES];
    [viewController release];
}      
IOS 記憶體洩漏 選擇圖檔上傳記憶體洩露

注意紅色語句,首先該image不是第一個ViewController建立的(沒有一個包含alloc/new/copy/mutableCopy的方法),是以不需要它來釋放image;然後第二個ViewController也沒有一個包含alloc/new/copy/mutableCopy的方法來建立image,是以,我天真的以為,第二個ViewController不需要對這個image的釋放負責。是以沒有在第二個ViewController的dealloc函數中釋放該image。

其實這是錯誤的,因為viewController.image = image;這行代碼會讓image的引用計數+1,這個image是我們自己建立的property,是以在其隐含的setImage方法中對image retain了一次。是以第二個ViewController有責任在dealloc中release該image屬性。

OK,那麼現在隻要在第二個ViewController中的dealloc中release該image,問題解決。

第三個問題

剩下就是這個神秘的UIStatusBarHideAnimationParameters。

IOS 記憶體洩漏 選擇圖檔上傳記憶體洩露

經不斷測試,發現出現這種情況的原因是,進入相冊,不選圖,取消之後出來,那麼就會發生一次記憶體洩漏。

剛開始還以為是我在工程的Info.plist檔案中添加的用于隐藏狀态欄的原因

IOS 記憶體洩漏 選擇圖檔上傳記憶體洩露

可是删掉之後還是出現記憶體洩漏。于是隻能上網求助。

後來發現,我重寫了完成選擇照片的函數

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info

卻沒有重寫取消選擇照片的函數

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker

該函數的描述中有這麼一句:

Your delegate’s implementation of this method should dismiss the picker view by calling the dismissModalViewControllerAnimated: method of the parent view controller.

Implementation of this method is optional, but expected.

ok,那麼重寫這個函數,并在裡面調用[picker dismissViewControllerAnimated:YES completion:^(void){}];來讓相冊界面消失。

……

但是,問題依然存在!

StackOverFlow上有人遇到這個問題,有人建議:

Maybe you need to clear the delegate for your UIImagePickerController? Delegates can prevent objects from being properly deallocated. 

于是我在完成選擇照片或取消選擇照片的回調函數中添加一句pick.delegate = nil,

……

但是,問題依然存在!

然後,StackOverFlow有人說,

It's a bug in the SDK. 

然後,下了一下蘋果的官方源碼——一個打開相冊選擇圖檔的demo,發現竟然也會出現這個神秘的UIStatusBarHideAnimationParameters引發的記憶體洩漏!

最後,StackOverFlow有人建議,

There is a know issue with the 

uiimagepickercontroller

 with memory leaks.

Apple recommend that you only allocate and instantiate only one instance and store it somewhere for the life of the application (whilst running that is).

是以目前隻能把picker聲明為全局變量,避免多次alloc和release。

目前問題隻能解決到這裡了。

總結

1.記得必要時釋放你自己建立的property,不能一味依賴alloc,new,copy等字眼來決定是否release.

2.打開系統相冊再退出會發生記憶體洩漏,這是蘋果的Bug?

轉載請注明出處http://www.cnblogs.com/chenyg32/