Проблема освобождения памяти, используемой 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. Я нашел для них действительно полезное решение - создание имиджа изображения в делегате приложения, таким образом оптимизируя производительность и использование памяти в моем приложении.
Смотрите это сообщение в блоге