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

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

Мой вопрос заключается в следующем: когда я наблюдаю это в 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];
}

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

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

Ответ 1

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

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

Обходной путь, который я нашел (также предложение Kendall), заключается в том, чтобы оставить имя изображения пустым в файле 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;
}

Ответ 2

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

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

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

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

Ответ 3

Отделитесь от своих проблем с кешем imageName, ответ на ваш вопрос

У меня есть большой 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 и более поздней версии, хотя я не тестировал это сам, сам.

Ответ 4

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

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

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

@implementation UIImage(imageNamed_Hack)

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

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

@end

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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