Проблема освобождения памяти, используемой UIImageViews с довольно большим изображением в UIScrollView

У меня есть большой UIScrollView, в который я помещаю 3-4 довольно больших (320x1500 пикселей или около того) изображения UIImageView. Я добавляю эти UIImageViews к представлению прокрутки внутри моих файлов NIB. У меня есть один выход на моем контроллере, и это для UIScrollView. Я использую свойство (nonatomic, 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 ответов

Решение

Я разгадал тайну - и я уверен, что это ошибка со стороны 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;
}

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

У меня есть большой UIScrollView, в который я помещаю 3-4 довольно больших (320x1500 пикселей или около того) изображения 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 и выше, хотя я сам этого не проверял.

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

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

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

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

Завершение ответа Bdebeez.

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

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

@implementation UIImage(imageNamed_Hack)

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

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

@end

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

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

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

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

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

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

Другие вопросы по тегам