在實際項目中,用戶在上傳圖片時,有時會一次性上傳大量的圖片。在上傳圖片前,我們要進行一系列操作,比如:旋轉圖片為正確方向,壓縮圖片等,這些操作需要將圖片加載到內存中,這時候會遇到的一個問題是內存劇增,導致內存不夠用,從而出現閃退的問題,下面對內存的使用做詳細分析.
一、內存分析,非優化
我在測試項目中,重復加載了一張圖片1000次,首先加載圖片到內存,然后進行壓縮操作,釋放內存
for (int i = 0; i <= 1000; i ++) {
//1.首先我們獲取到需要處理的圖片資源的路徑
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"upload" ofType:@"PNG"];
//2.將圖片加載到內存中,我們使用了alloc關鍵字,在使用完后,可以手動快速釋放掉內存
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
//3.這一步我們將圖片進行了壓縮,并得到一個autorelease類型實例
UIImage *image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
//4.釋放掉2步驟的內存
[image release];
}
上面的代碼看起來沒有任何問題,可以說是一種標準的代碼寫法,在每一步驟中都對內存做了小心的處理,我們來看一下,實際的內存使用情況:
在上圖中可以看到,我們的操作在沒有任何問題的情況下,在加載大量圖片時,還是會造成內存的劇增
可以看到自動釋放內存時,圖片占用的內存并沒有立即釋放掉
[圖片上傳失敗...(image-98bb33-1513839329366)]
在上圖中可以看到,我們的操作在沒有任何問題的情況下,在加載大量圖片時,還是會造成內存的劇增
[圖片上傳失敗...(image-701454-1513839329365)]
可以看到自動釋放內存時,圖片占用的內存并沒有立即釋放掉
[圖片上傳失敗...(image-9f26ae-1513839329364)]
這些資源沒有立即釋放的資源,占用了寶貴的內存資源,最終使程序被kill
三優化后的內存使用
上面程序被kill,是因為程序的內存使用問題,在上面的代碼中,我們每一步都對內存做了非常小心的處理,但是在加載大量的圖片時,還是會出現問題。其根本原因就是autorelease惹的禍,autorelease自動釋放內存,并不會立即把內存釋放掉,而是要等到下一個事件周期才會釋放掉。問題是一些資源我們不得不使用autorelease類型,比如作為函數的返回值,而且系統api及項目是的大部分也都是這么做的,如果全都依靠我們手動釋放很容易造成內存泄漏。
記?。篘SAutoreleasePool里面的維護了一個NSMutableArray數組,所有標記為autorelease的對象都會被添加都該數組中。只有當pool對象被drain的時候,才會去遍歷該數組,若retainCount為0則釋放內存,不為零就發生內存泄露!OC已經為我們建立一個pool對象,但是該pool對象需要比較久的時間才能drain掉,因此在一些遍歷處理的場景中,需要我們手動去建立pool對象,并手動drain掉。
for (int i = 0; i <= 1000; i ++) {
//創建一個自動釋放池
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"PNG"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
UIImage *image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
[image release];
//將自動釋放池內存釋放,它會同時釋放掉上面代碼中產生的臨時變量image2
[pool drain];
}
優化后的,內存使用情況
[圖片上傳失敗...(image-35425-1513839372453)]
可用內存不再劇減
CGImage及UIImage的數據由原來的220多減少到6-7個
可以看到使用了 NSAutoreleasePool后,加載大量圖片的時候內存也不會出現問題
四、自動釋放池概述
(1)自動釋放池被置于一個堆棧中,雖然它們通常被稱為被“嵌套”的。當您創建一個新的自動釋放池時,它被添加到堆棧的頂部。當自動釋放池被回收時,它們從堆棧中被刪除。當一個對象收到送autorelease消息時,它被添加到當前線程的目前處于棧頂的自動釋放池中。你不能向自動釋放池發送autorelease或retain消息。Application Kit會在一個事件周期(或事件循環迭代)的開端—比如鼠標按下事件—自動創建一個自動釋放池,并且在事件周期的結尾釋放它,因此您的代碼通常不必關心。 有三種情況您應該使用您自己的自動釋放池:
如果您正在編寫一個不是基于Application Kit的程序,比如命令行工具,則沒有對自動釋放池的內置支持;您必須自己創建它們。
如果您生成了一個從屬線程,則一旦該線程開始執行,您必須立即創建您自己的自動釋放池;否則,您將會泄漏對象。
如果您編寫了一個循環,其中創建了許多臨時對象,您可以在循環內部創建一個自動釋放池,以便在下次迭代之前銷毀這些
對象。這可以幫助減少應用程序的最大內存占用量。
(2) release和drain之間的差異
在引用計數環境下,release和drain一樣,會直接自動釋放池l對象。
在GC(垃圾回收)環境下,release是一個no-op(空操作),drain會觸發垃圾回收(如果自上次垃圾回收以來分配的內存大于當前的閾值)。
通常情況下,您都應該使用drain而不是使用release來銷毀自動釋放池。
-drain方法只適用于Mac OS X10.4(Tiger)及更高版本。
PS:如果項目使用的是ARC機制的,則在相同的位置使用@autoreleasepool {},
即,
for (int i = 0; i <= 1000; i ++) {
//創建一個自動釋放池
@autoreleasepool {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"PNG"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
UIImage *image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
}
}
在實際項目中,用戶在上傳圖片時,有時會一次性上傳大量的圖片。在上傳圖片前,我們要進行一系列操作,比如:旋轉圖片為正確方向,壓縮圖片等,這些操作需要將圖片加載到內存中,這時候會遇到的一個問題是內存劇增,導致內存不夠用,從而出現閃退的問題,下面對內存的使用做詳細分析.
一、內存分析,非優化
我在測試項目中,重復加載了一張圖片1000次,首先加載圖片到內存,然后進行壓縮操作,釋放內存
for (int i = 0; i <= 1000; i ++) {
//1.
首先我們獲取到需要處理的圖片資源的路徑
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"upload"ofType:@"PNG"];
//2.
將圖片加載到內存中,我們使用了
alloc
關鍵字,在使用完后,可以手動快速釋放掉內存
UIImage *image = [[UIImage alloc]initWithContentsOfFile:filePath];
//3.
這一步我們將圖片進行了壓縮,并得到一個
autorelease
類型實例
UIImage *image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
//4.
釋放掉
2步驟的內存
[image release];
}
上面的代碼看起來沒有任何問題,可以說是一種標準的代碼寫法,在每一步驟中都對內存做了小心的處理,我們來看一下,實際的內存使用情況:
[圖片上傳失敗...(image-87484c-1513838509643)]
在上圖中可以看到,我們的操作在沒有任何問題的情況下,在加載大量圖片時,還是會造成內存的劇增
[圖片上傳失敗...(image-837e1a-1513838509638)]
可以看到自動釋放內存時,圖片占用的內存并沒有立即釋放掉
[圖片上傳失敗...(image-9d2b2b-1513838509636)]
這些資源沒有立即釋放的資源,占用了寶貴的內存資源,最終使程序被kill
三優化后的內存使用
上面程序被kill,是因為程序的內存使用問題,在上面的代碼中,我們每一步都對內存做了非常小心的處理,但是在加載大量的圖片時,還是會出現問題。其根本原因就是
autorelease
惹的禍,
autorelease自動釋放內存,并不會立即把內存釋放掉,
而是要等到下一個事件周期才會釋放掉
。問題是一些資源我們不得不使用autorelease類型,比如作為函數的返回值,而且系統api及項目是的大部分也都是這么做的,如果全都依靠我們手動釋放很容易造成內存泄漏。
記?。篘SAutoreleasePool里面的維護了一個NSMutableArray數組,所有標記為autorelease的對象都會被添加都該數組中。
只有當pool對象被drain的時候,
才會去遍歷該數組,若retainCount為0則釋放內存,不為零就發生內存泄露!OC已經為我們建立一個pool對象,但是該pool對象需要比較久的時間才能drain掉,因此在一些遍歷處理的場景中,需要我們手動去建立pool對象,并手動drain掉。
for (int i = 0; i <= 1000; i ++) {
//
創建一個自動釋放池
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString*filePath = [[NSBundle mainBundle]pathForResource:@"test"ofType:@"PNG"];
UIImage*image = [[UIImage alloc] initWithContentsOfFile:filePath];
UIImage *image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
[image release];
//
將自動釋放池內存釋放,它會同時釋放掉上面代碼中產生的臨時變量
image2 [pool drain];
}
優化后的,內存使用情況
[圖片上傳失敗...(image-169621-1513838509642)]
可用內存不再劇減
CGImage及UIImage的數據由原來的220多減少到6-7個
可以看到使用了
NSAutoreleasePool
后,加載大量圖片的時候內存也不會出現問題
四、自動釋放池概述
(1)
自動釋放池被置于一個堆棧中,雖然它們通常被稱為被“嵌套”的。當您創建一個新的自動釋放池時,它被添加到堆棧的頂部。當自動釋放池被回收時,它們從堆棧中被刪除。當一個對象收到送autorelease消息時,它被添加到當前線程的目前處于棧頂的自動釋放池中。
你不能向
自動釋放池發送autorelease或retain消息
。
Application Kit會在一個事件周期(或事件循環迭代)的開端—比如鼠標按下事件—自動創建一個自動釋放池,并且在事件周期的結尾釋放它,因此您的代碼通常不必關心。
有三種情況您應該使用您自己的自動釋放池:
如果您正在編寫一個不是基于Application Kit的程序,比如命令行工具,則沒有對自動釋放池的內置支持;您必須自己創建它們。
如果您生成了一個從屬線程,則一旦該線程開始執行,您必須立即創建您自己的自動釋放池;否則,您將會泄漏對象。
-
如果您編寫了一個循環,其中創建了許多臨時對象,您可以在循環內部創建一個自動釋放池,以便在下次迭代之前銷毀這些
對象。這可以幫助減少應用程序的最大內存占用量。
(2)
release和drain之間的差異
在引用計數環境下,release和drain一樣,會直接自動釋放池l對象。
在GC(垃圾回收)環境下,release是一個no-op(空操作),drain會觸發垃圾回收(如果自上次垃圾回收以來分配的內存大于當前的閾值)。
通常情況下,您都應該使用drain而不是使用release來銷毀自動釋放池。
-drain方法只適用于Mac OS X10.4(Tiger)及更高版本。
PS:如果項目使用的是ARC機制的,則在相同的位置使用
@autoreleasepool{},
即,
//
創建一個自動釋放池
@autoreleasepool
{
NSString*filePath = [[NSBundle mainBundle]pathForResource:@"test"ofType:@"PNG"];
UIImage *image = [[UIImage alloc]initWithContentsOfFile:filePath];
UIImage*image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
}
}