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