2012-04-29 3 views
0

Я пытаюсь понять, почему мое приложение использует большое количество памяти, а затем падает после нескольких предупреждений памяти. Инструмент VM Tracker показывает, что он использует ~30Mb в качестве грязной памяти. Выделение показывает 10-15 Мб, что не так много. Как только приложение имеет дело с отображением большого количества изображений (миниатюр), я полагал, что изображения - это то, на что я должен смотреть. Я не использую стандартный образ UIImage imageNamed, вместо этого использую imageWithData и делаю кэширование. Когда система отправляет предупреждение памяти, я очищаю хранилище кеша. Чтобы убедиться, что созданные мной изображения уничтожаются, когда они больше не нужны, я подклассифицировал UIImage и переопределил методы imageWithData, release и dealloc. Я вижу, что imageWithData вызывается, но release и dealloc никогда не вызываются. Вот как я это делаю:UIImage dealloc никогда не называется

BaseUIimage.h

@interface BaseUIimage : UIImage 

@end 

BaseUIimage.m

#import "BaseUIimage.h" 

@implementation BaseUIimage 

+ (id)imageWithData:(NSData *)data { 
    NSLog(@"UIImage imageWithData"); 
    return [UIImage imageWithData:data]; 
} 

- (id)retain { 
    NSLog(@"UIImage retain: %d", [self retainCount]); 
    return [super retain]; 
} 

- (oneway void)release { 
    NSLog(@"UIImage release: %d", [self retainCount]); 
    [super release]; 
} 

- (id)autorelease { 
    NSLog(@"UIImage autorelease: %d", [self retainCount]); 
    return [super autorelease]; 
} 

- (void)dealloc { 
    NSLog(@"UIImage deallocated"); 
    [super dealloc]; 
} 

@end 

Мой код кэширования:

.h

#import "BaseUIimage.h" 

@interface UIImageCached : BaseUIimage 

// CACHE 
+ (NSMutableDictionary*) cache; 
+ (void) cleanCache; 
+ (UIImageCached *)imageNamed:(NSString *)imageName; 
+ (UIImageCached *)retinaImageNamed:(NSString *)imageName; 
+ (UIImageCached *)imageFromPath:(NSString *)imagePath; 

. m

#import "UIImageCached.h" 

@implementation UIImageCached 

static NSMutableDictionary *data; 

+ (NSMutableDictionary*) cache { 
    if (data == nil) 
     data = [[NSMutableDictionary alloc] initWithCapacity:150]; 
    return data; 
} 

+ (void) cleanCache { 
    NSLog(@"Cache cleaned images: %d", data.count); 

    for (BaseUIimage *image in data) { 
     NSLog(@"image rc: %d", [image retainCount]); // always prints rc = 1 
    } 

    [data removeAllObjects]; 
} 

+ (UIImageCached *)imageFromPath:(NSString *)imagePath { 
    UIImageCached *image = (UIImageCached*)[self.cache objectForKey:imagePath]; 
    if (image == nil) { 
     NSData *imageData = [[NSData alloc] initWithContentsOfFile:imagePath options:NSDataReadingMappedIfSafe error:nil]; 
     image = (UIImageCached*)[UIImageCached imageWithData:imageData]; 
     [imageData release]; 

     if (image) { 
      [self.cache setObject:image forKey:imagePath]; 
      //NSLog(@"new cached image: #%d", self.cache.count); 
     } else { 
      //NSLog(@"can't cache image: #%d", self.cache.count); 
     } 
    } 

    return image; 
} 

+ (UIImageCached *)imageNamed:(NSString *)imageName { 
    NSString *extension = [imageName pathExtension]; 
    NSString *fileName = [imageName stringByDeletingPathExtension]; 
    NSString *fileLocation = [[NSBundle mainBundle] pathForResource:fileName ofType:extension]; 
    return [self imageFromPath:fileLocation]; 
} 

+ (UIImageCached *)retinaImageNamed:(NSString *)imageName { 
    UIImageCached *image = (UIImageCached*)[self.cache objectForKey:imageName]; 
    if (image == nil) { 
     NSString *extension = [imageName pathExtension]; 
     NSString *fileName = [imageName stringByDeletingPathExtension]; 

     float s = 1.0; 

     // retina filename support 
     if(!isIPAD && [[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { 
      s = [[UIScreen mainScreen] scale]; 
      if (s > 1) 
       fileName = NSTR2(@"%@%@", fileName, @"@2x");    
     } 

     NSString *fileLocation = [[NSBundle mainBundle] pathForResource:fileName ofType:extension]; 

     NSData *imgData = [[NSData alloc] initWithContentsOfFile:fileLocation options:NSDataReadingMappedIfSafe error:nil]; 

     BaseUIimage *tmpImage = [[BaseUIimage alloc] initWithData:imgData]; 

     [imgData release]; 

     image = (UIImageCached*)[UIImageCached imageWithCGImage:tmpImage.CGImage 
                scale:s 
               orientation:UIImageOrientationUp];   

     [tmpImage release]; 

     if (image) { 
      [self.cache setObject:image forKey:imageName]; 
      //NSLog(@"-- CACHE: new cached image: #%d", self.cache.count);   
     } else { 
      NSLog(@"-- CACHE: can't cache image: %@", fileLocation); 
     } 
    } else { 
     //NSLog(@"-- CACHE: read cached image"); 
    } 
    return image; 
} 

@end 

Почему выпуск и dealloc никогда не называются? Означает ли это, что экземпляры UIImage, которые я создаю, не освобождаются, и это является причиной роста виртуальной памяти?

+0

Вы когда-нибудь называли cleanCache? В противном случае кеш, похоже, сохраняет изображения. – fsaint

+0

Да.В этом методе в AppDelegate: - (void) applicationDidReceiveMemoryWarning: (приложение UIApplication *) { NSLog (@ "BTC_DashboardAppDelegate applicationDidReceiveMemoryWarning"); [UICmageCached cleanCache]; } –

ответ

3

Вы создали подкласс неправильно. В коде здесь:

@implementation BaseUIimage 

+ (id)imageWithData:(NSData *)data { 
    NSLog(@"UIImage imageWithData"); 
    return [UIImage imageWithData:data]; 
} 

... 

Вы не получаете экземпляр BaseUIimage, вы получаете регулярный UIImage, что означает изменённые выпуск/dealloc и т.д. не называются, так как они не являются частью Класс UIImage.

Вы должны изменить эту функцию:

@implementation BaseUIimage 

+ (id)imageWithData:(NSData *)data { 
    NSLog(@"UIImage imageWithData"); 
    return [super imageWithData:data]; 
} 

... 

который будет возвращать экземпляр вашего класса BaseUIimage. Теперь вы сможете увидеть, как ваши переопределенные методы вызываются.

+2

Это, безусловно, приведет к бесконечной рекурсии. Является ли 'т? –

+0

Да, извините, уже поздно. Я отредактировал ответ, используя super. –

+0

Это все еще не работает. В любом случае супер здесь UIImage. –

3

Дэйв Вуд был почти прав (его реализация все равно создаст UIImage). Проблема заключается в том, что

[UIImage imageWithData:data]; 

создаст UIImage, а не подкласс, который вы ожидаете от его создания. Чтобы проверить это, попробуйте выполнить следующую реализацию imageWithData:

+ (id)imageWithData:(NSData *)data { 
    NSLog(@"UIImage imageWithData"); 
    UIImage *im = [UIImage imageWithData:data]; 
    NSLog(@"%@",[im class]); 
    return im; 
} 

NSLog выводит UIImage. Не то, что вам нужно. Я предлагаю следующую реализацию:

+ (id)imageWithData:(NSData *)data { 
    NSLog(@"UIImage imageWithData"); 
    BaseUIimage *im = [[BaseUIimage alloc] initWithData:data]; 
    return [im autorelease]; 
} 

Это создаст образ класса BaseUIimage и, таким образом, ведет себя, как вы ожидаете. Приветствия.

+1

+1, да, вот что я имел в виду. :) –

+0

спасибо! но почему предыдущая реализация не работает? он работает, когда я подклассифицирую UIViewController, например. –

+2

Я не понимаю, как вы используете подкласс UIViewController. Однако [UIImage imageWithData: data] создаст объект класса UIImage независимо от того, где вы его называете. Ни в коем случае. Когда вы вызываете [UIImage imageWithData: data], вы говорите: «Эй, класс UIImage создайте мне экземпляр с этими данными». UIImage знает только UIImage, а не подклассы. Это «imageWithData:» - это метод фабрики класса. Другое дело, когда вы делаете [[BaseUIimage alloc] initWithData: data], где вы вызываете конструктор, который BaseUIimage наследует от UIImage. Теперь вопрос в том, что мое предложение решило вашу проблему? – fsaint

Смежные вопросы