Предупреждение памяти и авария (ARC) - как определить, почему это происходит?
Я начал использовать ARC недавно, и с тех пор я обвиняю его в каждой проблеме с памятью.:) Возможно, вы могли бы помочь мне лучше понять, что я делаю неправильно.
Мой текущий проект о CoreGraphics - графический чертеж, просмотры, заполненные эскизами и так далее. Я считаю, что при использовании ручного управления памятью не возникнет проблем, за исключением, может быть, нескольких зомби... Но на данный момент приложение просто падает каждый раз, когда я пытаюсь либо создать много миниатюр, либо перерисовать немного более сложный график.
Во время профилирования с помощью инструментов я вижу очень высокое значение в резидентной памяти, а также в грязной. Анализ кучи показывает довольно тревожный нерегулярный рост...
При рисовании всего нескольких эскизов резидентная память растет примерно на 200 МБ. Когда все нарисовано, память падает на почти то же значение, что и до рисования. Однако, с большим количеством эскизов, значение в резидентной памяти выше 400 МБ, и это явно приводит к сбою приложения. Я попытался ограничить количество эскизов, нарисованных в одно и то же время (NSOperationQueue и его maxConcurrentOperationCount), но поскольку освобождение так много памяти, кажется, занимает немного больше времени, это действительно не решило проблему.
Сейчас мое приложение в основном не работает, так как реальные данные работают со множеством сложных диаграмм - много эскизов.
Каждый эскиз создается с помощью этого кода, который я получил отсюда: (категория в UIImage)
+ (void)beginImageContextWithSize:(CGSize)size
{
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
if ([[UIScreen mainScreen] scale] == 2.0) {
UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
} else {
UIGraphicsBeginImageContext(size);
}
} else {
UIGraphicsBeginImageContext(size);
}
}
+ (void)endImageContext
{
UIGraphicsEndImageContext();
}
+ (UIImage*)imageFromView:(UIView*)view
{
[self beginImageContextWithSize:[view bounds].size];
BOOL hidden = [view isHidden];
[view setHidden:NO];
[[view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
[self endImageContext];
[view setHidden:hidden];
return image;
}
+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize
{
UIImage *image = [self imageFromView:view];
if ([view bounds].size.width != newSize.width ||
[view bounds].size.height != newSize.height) {
image = [self imageWithImage:image scaledToSize:newSize];
}
return image;
}
+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
[self beginImageContextWithSize:newSize];
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
[self endImageContext];
return newImage;
}
Есть ли какой-нибудь другой способ, который бы не питал столько памяти или что-то действительно не так с кодом при использовании ARC?
В другом месте, где происходит предупреждение о сбое + аварии, происходит слишком много перерисовки любого вида. Это не нужно быть быстрым, просто много раз. Память складывается до тех пор, пока она не сработает, и я не смогу найти что-то действительно ответственное за нее. (Я вижу растущую резидентную/грязную память в VM Tracker и рост кучи в инструменте Allocations)
Мой вопрос в основном заключается в следующем: как найти, почему это происходит? Мое понимание заключается в том, что для данного объекта нет владельца, который был выпущен как можно скорее. Моя проверка кода предполагает, что многие объекты вообще не выпущены, хотя я не вижу причин для этого. Я не знаю о каких-либо циклах сохранения...
Я прочитал "Переход к заметкам о выпуске ARC", статью "bbum", посвященную анализу кучи и, вероятно, десятку других. Отличается каким-то анализом кучи с ARC и без нее? Я не могу ничего сделать с помощью .
Спасибо за любые идеи.
ОБНОВЛЕНИЕ: (чтобы не заставить всех читать все комментарии и выполнять мои обещания)
Добиваясь моего кода и добавляя @autoreleasepool, где он имел какой-то смысл, потребление памяти снижалось. Самая большая проблема заключалась в вызове UIGraphicsBeginImageContext
из фонового потока. После исправления (см. Ответ @Tammo Freese для получения подробной информации) освобождение произошло достаточно скоро, чтобы не сбой приложения.
Моя вторая авария (вызванная многими перерисовками одного и того же графика) была полностью решена путем добавления CGContextFlush(context)
в конце моего метода рисования. Позор мне.
Небольшое предупреждение для любого, кто пытается сделать что-то подобное: использовать OpenGL. CoreGraphics недостаточно быстро для анимации больших рисунков, особенно на iPad 3. (сначала с сетчаткой)
Ответы
Ответ 1
Чтобы ответить на ваш вопрос: Идентификация проблем с предупреждениями о памяти и сбоями с ARC в основном работает, как и раньше, с ручным удержанием (MRR). ARC использует retain
, release
и autorelease
точно так же, как MRR, он только вставляет вызовы для вас и имеет некоторые оптимизации на месте, что в некоторых случаях должно даже снизить потребление памяти.
Относительно вашей проблемы:
В снимок экрана из инструментов, который вы опубликовали, отображаются всплески выделения. В большинстве случаев, с которыми я столкнулся до сих пор, эти спайки были вызваны слишком длинными автореализованными объектами.
-
Вы упомянули, что используете NSOperationQueue
. Если вы переопределите -[NSOperationQueue main]
, убедитесь, что вы завершаете весь контент метода в @autoreleasepool { ... }
. Пул авторесурсов уже может быть установлен, но он не гарантируется (и даже если он есть, он может быть примерно дольше, чем вы думаете).
-
Если 1. не помогло, и у вас есть цикл, обрабатывающий изображения, оберните внутреннюю часть цикла в @autoreleasepool { ... }
, чтобы немедленно очистить временные объекты.
-
Вы упомянули, что используете NSOperationQueue
. Поскольку iOS 4, рисование в графическом контексте в UIKit является потокобезопасным, но если документация правильная, UIGraphicsBeginImageContext
следует еще только вызывать в основном потоке! Обновление: теперь в документах указано, что с iOS 4 функция может быть вызвана из любого потока, так как на самом деле это необязательно! Чтобы быть в безопасности, создайте контекст с помощью CGBitmapContextCreate
и выберите изображение с CGBitmapContextCreateImage
. Что-то в этом роде:
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
// draw to the context here
CGImageRef newCGImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *result = [UIImage imageWithCGImage:newCGImage scale:scale orientation: UIImageOrientationUp];
CGImageRelease(newCGImage);
return result;
Ответ 2
Итак, вы ничего не делаете относительно управления памятью (их нет!) выглядит ненадлежащим. Однако вы упоминаете использование NSOperationQueue. Те UIGraphics... звонки помечены как не потокобезопасные, но другие заявили, что они относятся к iOS 4 (я не могу найти окончательного ответа на этот вопрос, но напомню, что это правда.
В любом случае вы не должны называть эти методы класса из нескольких потоков. Вы можете создать очередную диспетчерскую очередь и выполнить всю работу с ней, чтобы обеспечить однопоточное использование.
Конечно, здесь отсутствует то, что вы делаете с изображениями после их использования. Возможно, вы их каким-то образом сохраняете, что не очевидно. Вот несколько трюков:
-
в любом из ваших классов, которые используют множество изображений, добавьте метод dealloc(), который просто регистрирует его имя и некоторый идентификатор.
-
вы можете попытаться добавить dealloc в UIImage, чтобы сделать то же самое.
-
попробуйте подключить ваше приложение, используя простейшую возможную настройку - наименьшее количество изображений и т.д., чтобы вы могли убедиться, что на самом деле изображения и их владельцы получают dealloc'd.
-
когда вы хотите убедиться, что что-то выпущено, установите ivar или свойство в nil
Прошлым летом я преобразовал проект в 100 файлов в ARC, и он отлично работал из коробки. Я преобразовал несколько проектов с открытым исходным кодом в ARC, а также только с одной проблемой, когда я неправильно использовал мост. Эта технология является твердосплавной.
Ответ 3
Это не ответ на ваш вопрос, но я пытался решить подобные проблемы задолго до появления ARC.
Недавно я работал над приложением, которое кэшировало изображения в памяти и освобождало их всех после получения предупреждения о памяти. Это работало нормально, пока я использовал приложение с нормальной скоростью (без сумасшедшего нажатия). Но когда я начал генерировать много событий, и многие изображения начали загружаться, приложение не смогло получить предупреждение о памяти и оно сбой.
Я однажды написал тестовое приложение, которое создавало множество объектов с автореализацией после нажатия кнопки. Я смог быстрее (и создавать объекты) быстрее, чем ОС удалось освободить память. Память медленно увеличивалась, поэтому после значительного времени или просто с использованием более крупных объектов я бы наверняка сбил приложение и заставил устройство перезагружаться (выглядит действительно эффективно;)). Я проверил это с помощью инструментов, которые, к сожалению, повлияли на тест и сделали все медленнее, но я полагаю, что это верно и при использовании инструментов.
В то же время я работал над большим проектом, который довольно сложный и имеет много пользовательского интерфейса, созданного из кода. У него также много обработки строк, и никто не заботился о том, чтобы использовать release - в последний раз было несколько тысяч вызовов автоответчика. Таким образом, после 5 минут немного расширенного использования этого приложения, он сбой и перезагрузка устройства.
Если я прав, то OS/логика, которая отвечает за фактическое освобождение памяти, не достаточно быстра или не имеет достаточно высокого приоритета, чтобы сэкономить приложение при сбое при выполнении большого количества операций с памятью. Я никогда не подтверждал эти подозрения, и я не знаю, как решить эту проблему, кроме простого сокращения выделенной памяти.