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