2013-05-17 22 views
0

我有我創建可以很容易地在我的應用程序加載圖像的輔助類 - 它獲得的用於ALOT:在iOS中加載放棄內存的圖像的輔助方法 - 我如何避免這種情況?

@implementation Helpers 

+(UIImage *) getThumbnailImageIfExists:(NSString *)ItemSKU withManufacturer: (NSNumber *) aManufacturerID { 

    @autoreleasepool { 

    NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:ItemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"]; 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsPath = [paths objectAtIndex:0]; 
    NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]]; 
    NSData *imageData = [NSData dataWithContentsOfFile:savePath]; 

    if (imageData==nil) 
    { 
     return nil; 
    } 

    return [UIImage imageWithData:imageData]; 

    } 
} 

@end 

我使用Profiler來看看爲什麼我的應用程序總是崩潰。我正在使用Leaks工具和Heapshots來查看被遺棄的內存中存在什麼東西 - 看起來像是在殺死我。

我該如何解決這個問題?這是一個轉換爲ARC的舊項目。

有什麼想法?

enter image description here

enter image description here

+2

你確定問題在這裏,而不是使用這些圖像的任何代碼? – rmaddy

+0

不,我可能在將這個項目轉換爲ARC時做了一些假設 - 正如你所看到的我的堆積增長是荒謬的 – Slee

+0

@rmaddy它看起來你是對的,這是一個開始支持iOS 4的舊項目它有許多代表沒有設置爲弱點,所以整個觀點被拋棄到記憶中 - 作爲答案添加,所以我可以給你信用。 – Slee

回答

1

你正在創建一個自動釋放池內部的自動釋放的對象(imageWithData),返回這一點,但隨後立即排水池中。最簡單的解決方法是刪除該自動釋放池。爲什麼有這個池呢?只需立即排空NSData?但你並不需要一個NSData在所有的,因爲你可以只直接檢索圖像:

@implementation Helpers 

+ (UIImage *) getThumbnailImageIfExists:(NSString *)ItemSKU withManufacturer: (NSNumber *) aManufacturerID { 

    NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:ItemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"]; 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsPath = [paths objectAtIndex:0]; 
    NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]]; 

    return [UIImage imageWithContentsOfFile:savePath]; 
} 

@end 

如果你真的想確保各種字符串和數組變量(即fileNamepathsdocumentsPathsavePath)不會被放到調用者的自動釋放池中,你可以解決這個問題,但我不知道這是多麼的重要(至少與池中的NSData相比)。


考慮這個替代實現:

+ (UIImage *)getThumbnailImageIfExists:(NSString *)itemSKU withManufacturer:(NSNumber *)aManufacturerID 
{ 
    UIImage *image; 
    static NSString *documentsPath; 
    static NSCache *cache; 

    // create docsPath and cache once and only once 

    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
     documentsPath = searchPaths[0]; 
     cache = [[NSCache alloc] init]; 
     cache.countLimit = 100; 
    }); 

    // now do your image retrieval 

    @autoreleasepool { 
     NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:itemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"]; 
     NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]]; 

     image = [cache objectForKey:savePath]; 
     if (!image) 
     { 
      image = [[UIImage alloc] initWithContentsOfFile:savePath]; // note, not an autoreleased object 
      [cache setObject:image forKey:savePath]; 
     } 
    } 

    return image; 
} 

我做了幾件事情在這裏:

  1. 像以前一樣,我已經去除了不必要的NSData邏輯。無需將文件加載到NSData中,然後從中創建UIImage,然後僅丟棄NSData

  2. 如果您對同一個SKU /製造商重複調用此圖像,則使用NSCache來存儲加載的圖像會節省大量的內存(以及性能改進)。如果您碰巧多次請求同一圖像,它會阻止您創建重複圖像。使用NSCache解決了這個問題。通過我的圖片文件名鍵入NSCache,這是一個方便的使用鍵(儘管您也可以使用一些由製造商代碼和SKU組成的字符串;這取決於您)。

  3. 我援用的dispatch_once自己設置兩個靜態變量:

    • documentsPath(有,如果你打電話的時候的這數以萬計的可觀察到的影響,改善恐怕不會可觀察的,如果你調用這個只有幾百倍)

    • cache(如果你想你的緩存將調用的情況下,堅持這種方法,你需要或者做類似的東西,使得它static,這是爲了確保它仍然存在,但通過dispatch_once一旦將它設置)

    坦率地說,我傾向於在適當的init方法移動documentsPath和/或cache一些單一實例的實例變量並設置這些變量,而比使用dispatch_once,但我試圖告訴你如何通過修改你與我們分享的方法來做到這一點。

  4. 真的很小的變化,但我總是用變量名稱來使用camelCase(以小寫字母開頭),所以我將ItemSKU更改爲itemSKU。例如,當我使用@autoreleasepool塊時,通常不需要這樣做,除非您在單個for循環內調用此方法的次數很多。如果這些是在表格視圖或集合視圖中使用的縮略圖,則不需要@autoreleasepool塊。但是我保留在那裏以防萬一這些特殊情況適用。

    就個人而言,我使用圍繞自包含代碼塊的@autoreleasepool塊,而不是返回某個值的代碼。但是如果你遇到這種情況,你可以像上面那樣做。

使用該cache都會有很大的影響(無論是在內存消耗和性能方面)如果你調用此方法一次以上相同的圖像。使用staticdispatch_once代替documentsPath的性能影響不大,但如果您打電話給很多,那麼它會變得很明顯,並且您可能會考慮優化。

使用@autoreleasepool塊可以在內存增加的情況下使用,但在完成後會回落到合理的水平,但您只是想減少「高水位」。如果問題是內存永遠不會下降,那麼自動釋放池不會幫助你;問題在於其他地方。

你應該自己動手,通過分析器運行它,並檢查性能和內存使用情況。就個人而言,我通常會關注緩存的使用,不要太擔心@autoreleasepool,除非有什麼特別的關於你如何調用這種方法(例如,你在一個for循環中稱它爲成千上萬次),但是這是需要考慮的事情。對於大多數情況下,真正的好處將來自使用緩存,而不是@autorelease塊。

+0

我只是在嘗試任何與autorelease池,這個解決方案仍然給我相同數量的堆增長:( – Slee

+0

@Slee它似乎不可能有可能,它有相同數量的堆增長,因爲我們已經取出其中一個最大的項目是'NSData',顯然你不能擺脫圖像(因爲這就是你要返回的東西,你大概需要這個)我懷疑rmaddy是對的,問題在於調用你可以分享它在做什麼嗎?另外,你是否可以多次調用這個縮略圖?如果是這樣,你應該對這些圖像使用'NSCache',這將大大減少內存使用。 – Rob

+0

@Slee I'我真正的問題是如何調用getThumbnailImageIfExists方法,但希望我的擴展答案能讓你思考其他想法,順便說一下,我假定你使用的是ARC,如果不是, ,你需要適當的'release'和'autorelease'語句。 – Rob

相關問題