Проблема освобождения памяти, используемой UIImageViews с довольно большим изображением в UIScrollView - PullRequest
11 голосов
/ 14 ноября 2008

У меня есть большой UIScrollView, в который я помещаю 3-4 довольно больших (320х1500 пикселей или около того) изображения UIImageView. Я добавляю эти UIImageViews к представлению прокрутки внутри моих файлов NIB. У меня есть один выход на моем контроллере, и это для UIScrollView. Я использую для этого свойство (неатомное, retain) и синтезирую его.

У меня такой вопрос: когда я наблюдаю это в Memory Monitor, я вижу, что используемая память значительно увеличивается, когда загружается представление со всеми этими изображениями (как и ожидалось). Но когда я покидаю вид, он и его контроллер освобождаются, но, похоже, не сдаются рядом с памятью, которую они заняли. Когда я урезал одно из этих представлений (их несколько в моем приложении) до 1-3 изображений, которые были размером 320x460 и оставляли все остальное тем же самым, он прекрасно восстанавливает память.

Есть ли проблемы с использованием изображений такого большого размера? Что-то не так в этом коде (вставлено ниже)?

Это фрагмент из viewController, который вызывает проблемы.

- (CGFloat)findHeight 
{
    UIImageView *imageView = nil;
    NSArray *subviews = [self.scrollView subviews];

    CGFloat maxYLoc = 0;
    for (imageView in subviews)
    {
            if ([imageView isKindOfClass:[UIImageView class]])
            {
                    CGRect frame = imageView.frame;

                    if ((frame.origin.y + frame.size.height) > maxYLoc) {
                            maxYLoc  = frame.origin.y;
                            maxYLoc += frame.size.height;
                    }
            }
    }
    return maxYLoc;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.scrollView setContentSize:CGSizeMake(320, [self findHeight])];

    [self.scrollView setCanCancelContentTouches:NO];
    self.scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    self.scrollView.clipsToBounds = YES;                
    self.scrollView.scrollEnabled = YES;

    self.scrollView.pagingEnabled = NO;
}

- (void)dealloc {
    NSLog(@"DAY Controller Dealloc'd");
    self.scrollView = nil;
    [super dealloc];
}

ОБНОВЛЕНИЕ: я заметил еще одно странное явление. Если я не использую прокрутку на представлении, это, кажется, висит на памяти. Но если я прокручиваю кучу и проверяю, что все UIImageViews стали видимыми в одной точке, он освободит и восстановит большую часть потерянной памяти.

ОБНОВЛЕНИЕ2: причина, по которой я спрашиваю об этом, заключается в том, что мое приложение на самом деле падает из-за нехватки памяти. Я не возражал бы, если бы это было просто кэширование и использование дополнительной памяти, но, похоже, он никогда не освобождает ее - даже в условиях didReceiveMmoryWarning

Ответы [ 7 ]

15 голосов
/ 14 ноября 2008

Я разгадал тайну - и я уверен, что это ошибка со стороны Apple.

Как предложил Кендалл (спасибо!), Проблема заключается в том, как InterfaceBuilder загружает изображения из файла NIB. Когда вы initFromNib, все UIImageViews будут инициироваться с UIImage, используя imageNamed: метод UIImage. Этот вызов использует кэширование для изображения. Обычно это то, что вы хотите. Однако с очень большими изображениями и, кроме того, изображениями, которые прокручиваются далеко от видимой области, кажется, что они не подчиняются предупреждениям памяти и не сбрасывают этот кэш. Это то, что я считаю ошибкой на стороне Apple (пожалуйста, прокомментируйте, если вы согласны / не согласны - я хотел бы представить это, если другие согласятся). Как я сказал выше, память, используемая этими изображениями, кажется, освобождается, если пользователь прокручивает достаточно, чтобы все это было видно.

Обходной путь, который я нашел (также предложение Кендалла), - оставить имя изображения пустым в файле NIB. Таким образом, вы выкладываете свои элементы UIImageView как обычно, но не выбираете изображение. Затем в своем коде viewDidLoad вы входите и загружаете изображение, используя вместо этого imageWithContentsOfFile :. Этот метод НЕ кэширует изображение и, следовательно, не вызывает проблем с памятью при сохранении больших изображений.

Конечно, imageNamed: намного проще в использовании, потому что он по умолчанию используется в пакете, а не в поиске пути. Однако вы можете получить путь к пакету с помощью следующего:

NSString *fullpath = [[[NSBundle mainBundle] bundlePath];

Сложив все это вместе, вот как это выглядит в коде:

NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]];
UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath];
imageView.image = loadImage;

Итак, добавляя это к моему коду выше, полная функция выглядит так:

- (CGFloat)findHeight 
{
    UIImageView *imageView = nil;
    NSArray *subviews = [self.scrollView subviews];

    CGFloat maxYLoc = 0;
    for (imageView in subviews)
    {
        if ([imageView isKindOfClass:[UIImageView class]])
        {
            CGRect frame = imageView.frame;

            if ((frame.origin.y + frame.size.height) > maxYLoc) {
                maxYLoc  = frame.origin.y;
                maxYLoc += frame.size.height;
            }

            NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]];
            NSLog(fullpath);


            UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath];
            imageView.image = loadImage;
        }
    }
    return maxYLoc;
}
4 голосов
/ 23 октября 2009

Отдельно от вашего изображения Именованные вопросы кеширования, ответ на ваш вопрос

У меня есть большой UIScrollView, в который Я ставлю 3-4 довольно большой (320х1500 пикселей или около того) UIImageView изображения плитки. [...] Есть ли проблемы с использованием изображения это большое?

Находится в заголовке документации UIImage:

You should avoid creating UIImage objects that are greater than 1024 x 1024 in size.
Besides the large amount of memory such an image would consume, you may run into 
problems when using the image as a texture in OpenGL ES or when drawing the image 
to a view or layer.

Кроме того, Apple утверждает, что исправила проблему с очисткой кэша imageNamed в версии 3.0 и выше, хотя я сам не проверял это подробно.

4 голосов
/ 14 ноября 2008

Это может быть система, кеширующая ссылки на ваши изображения в памяти, при условии, что в изображениях из медиабраузера в IB только что добавлено лекарство, код под которым, вероятно, использует UIImage imageNamed: метод ...

Вы можете попробовать загрузить все изображения с помощью imageWithContentsOfFile: или imageWithData: и посмотреть, будет ли оно вести себя одинаково (перетаскивая незаполненный UIImageViews в IB и устанавливая содержимое в viewDidLoad:).

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

Если это кеш, то, вероятно, все в порядке, хотя система освободит его при необходимости (вы также пытались нажать Предупреждение симуляции памяти в симуляторе?)

1 голос
/ 10 ноября 2009

Завершение ответа Бдебиза.

Одна хорошая идея - переопределить imageNamed:, вызывая imageWithContentsOfFile:.

Вот идея кода:

@implementation UIImage(imageNamed_Hack)

+ (UIImage *)imageNamed:(NSString *)name {

    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], name ] ];
}

@end

Примечание: С этим переопределением у вас не будет загрузки кэша UIImages, если вам это нужно, вам придется реализовать свой собственный кэш.

0 голосов
/ 27 августа 2009

Существует известная проблема - утечка памяти в imageName. Я нашел для него действительно полезное решение - создание денежного изображения в делегате приложения, таким образом оптимизируя производительность и использование памяти в моем приложении. Смотрите это сообщение в блоге

0 голосов
/ 14 ноября 2008

Из того, что я узнал о его поведении при управлении памятью, является то, что представления не будут освобождены, если только в памяти недостаточно. Попробуйте официальную демонстрацию, такую ​​как SQLBooks: 1. Запустите с Leaks Monitor 2. Запустите все просмотры, которые у него есть. 3. Вернитесь к основному виду. 4. Вы заметите, что уровень использования памяти остается прежним. Как сказал Кендалл, это может быть кэшировано?

Я думаю, что вам не следует обращать внимание на этот шаблон использования памяти - потому что, когда новые изображения будут указывать на UIScrollView, старые объекты изображений будут освобождены, и память все равно будет освобождена для новых изображений.

0 голосов
/ 14 ноября 2008
- (void)dealloc {
    NSLog(@"DAY Controller Dealloc'd");
    [self.scrollView release];
    [super dealloc];
}

дайте этому выстрелу, ваше определение @property () запрашивает его сохранение, но вы явно не освобождали объект

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...