Лучшее место для очистки приложений глобальных ресурсов?

СТОП НАЖМИТЕ ОК, прежде чем вы увидите слово retainCount в следующем вопросе, пожалуйста, перейдите к РЕДАКТИРОВАТЬ внизу, где я заявил, что я перестал его использовать.

Мое приложение Какао, которое использует MRR, создает много глобальных ресурсов, которые я загружаю в main(), до NSApplicationMain() называется. Как NSApplicationMain() не возвращается, я подключил очистку этих ресурсов с помощью atexit(), как это:

atexit(cleanup);

if (![CocoaUtil initCocoaUtil] ||
    ![PreferenceController initPreferenceController] ||
    ![ResourceManager initResourceManager])
{
    criticalAlertPanel(@"Failed to initialize application",
                       @"Failed to initialize application");
    return 4;
}

retval = NSApplicationMain(argc, (const char **)argv);

тем не мение cleanup() вызывается до любого из взглядов в моем NSDocument подкласс dealloc'd (у меня нет сообщения журнала, чтобы показать это), и, следовательно, подсчет ссылок объектов в глобальных ресурсах иногда > 1, Я слишком осторожен и пытаюсь предотвратить утечки памяти, используя этот метод для освобождения моих глобальных ресурсов:

+ (void)fullRelease:(id)obj
             format:(NSString *)format, ...
{
    if (obj == nil)
        return;

    NSUInteger retainCount = [obj retainCount];
    if (retainCount > 1)
    {
        va_list va;
        va_start(va, format);
        NSString *objDesc = [[NSString alloc] initWithFormat:format arguments:va];
        logwrn(@"%@ has a reference count of %lu", objDesc, retainCount);
        [objDesc release];
    }

    while (retainCount > 0)
    {
        [obj release];
        retainCount--;
    }
}

Мой журнал показывает следующее:

12:15:04.954 INF -[AppController applicationDidFinishLaunching:] Application launched
12:15:06.702 INF -[AppController applicationShouldTerminate:] Application terminating
12:15:06.703 INF -[AppController applicationWillTerminate:] Application terminating
12:15:06.705 DBG cleanup Cleaning-up
12:15:06.705 INF +[ResourceManager finiResourceManager] Cleaning up
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[2] has a reference count of 2
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[3] has a reference count of 2
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[4] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[5] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[6] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[7] has a reference count of 2
12:15:06.711 WRN +[CocoaUtil fullRelease:format:] _images[8] has a reference count of 2
12:15:06.711 WRN +[CocoaUtil fullRelease:format:] _images[9] has a reference count of 2
12:15:06.721 DBG +[PreferenceController finiPreferenceController] Cleaning up
12:15:06.721 DBG +[CocoaUtil finiCocoaUtil] Cleaning up

Мой вопрос (наконец-то!):

Есть ли способ обеспечить очистку моего глобального ресурса после всех NSDocument случаи были уничтожены и перестали получать эти ложные негативы?

РЕДАКТИРОВАТЬ: я отцепил fullRelease позвони и просто выполнила нормальную release на моих ресурсах и инструментах не было обнаружено утечек памяти, так что все в порядке, но мне любопытно, почему NSDocument объекты, кажется, не выпускаются раньше atexit() называется.

4 ответа

Решение

Не отпускайте то, что вам не принадлежит!

Каждое удержание принадлежит кому-то другому. Только отправить release к объекту, чтобы сбалансировать ваши звонки new, alloc, copy, или же retain (NARC.) Такое поведение неизбежно вызовет сбой в производственном коде.

Похоже, вы хотите убедиться, что объект освобожден, а не просто позаботился. Если у вас есть объект, отпустите его один раз; другие ссылки на него принадлежат другим объектам. Возможно, у вас есть утечки памяти в вашем коде (мы не можем сказать только из этого примера кода), но они обычно могут быть обнаружены с помощью статического анализатора, инструментов и небольшого количества смазки для локтя.

Что еще более важно: операционная система освободит всю вашу память для вас, когда ваш процесс завершится. Это не является частью стандарта C, но это просто, как работают OS X и iOS, и это ожидаемое поведение на других платформах, которые поддерживают Objective-C. Поэтому вам не нужно делать ничего особенного для очистки при выходе из процесса, кроме, возможно, записи файлов на диск или чего-то подобного. Фактически, многие приложения Какао не удосуживаются выпустить что-либо, принадлежащее их делегатам, потому что операционная система быстрее освобождает память, чем она вызывает -release на тысячи предметов.

Не звони -retainCount !

Это ложь. Легко и просто. Это включает в себя временные ссылки, используемые Какао, что наиболее важно, и вы никогда не должны пытаться вмешиваться в них. -retainCount это отравленный символ.

Некоторые заметки:

  • не использовать retainCount; см. ссылки на http://whentouseretaincount.com/ для многих деталей

  • Обработчики atexit() бесполезны в программировании высокого уровня. При вызове приложение находится в относительно неопределенном состоянии. Фреймворки будут разрушать некоторые вещи, но, как вы заметили, будет множество объектов, которые никогда не будут освобождены. atexit() может не вызываться вообще, в некоторых случаях.

  • Вы не можете полагаться на завершение приложения для выполнения какой-либо очистки состояния. Пользователь может принудительно закрыть ваше приложение. Система тоже может или принудительно перезагружена. Поведение при завершении должно рассматриваться как оптимизация; Можете ли вы сделать некоторые вещи, которые сделают следующий запуск быстрее.

  • во время завершения приложения нет необходимости освобождать что-либо. Система восстановит все ресурсы приложения после завершения независимо от состояния приложения. Другими словами, объекты, оставленные в памяти при завершении, не являются утечками.

  • В общем, инструменты обнаружения "утечек" могут использоваться только для устранения очевидных проблем. Утечки обнаруживает только утечки памяти. Он не может обнаружить увеличение памяти, где аккрецируемые объекты все еще как-то связаны с глобальным. Аккреция технически не протекает, но может легко стать источником проблем.

  • Анализ HeapShot обнаружит как утечки, так и увеличение памяти.

  • Рассмотрите возможность миграции вашего приложения на использование ARC. Одним из руководящих принципов ARC является то, что компилятор полностью знает срок жизни объектов. Выражения, которые неоднозначны при MRR, запрещены при ARC (в некоторых случаях без надлежащей разметки). Кроме того, компилятор и анализатор ARC может выполнить более глубокий анализ вашего кода, обнаружив множество проблем, которые могут быть довольно тонкими.

Отключите внезапное завершение, не используйте глобальные переменные, а просто используйте обычные правила подсчета ссылок. В некоторых случаях вам нужно будет разорвать сильные циклические ссылки или вручную очистить переменные экземпляра ваших объектов. Наконец, может быть более полезно сделать паузу / прерывание перед основным возвратом и выполнить heap чтобы увидеть, что на самом деле (утечка) на этом этапе.

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

Не стоит так чистить - вы знаете лучше:)

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

Как и все остальные говорили: освободи только то, что у тебя есть. Ваше приложение может не рухнуть для вас сейчас, но вам просто повезло. Это может привести к сбою на другом Mac, если вы сделаете это, или через два часа, или как угодно.

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

Например, есть уведомление, которое Mac OS X отправляет до выхода из приложения, NSApplicationWillTerminate. Пусть ваш объект зарегистрируется для этого, и когда он его получит, он отправит серверу прощание (и помните, что он это уже сделал). Таким образом, когда ваше приложение завершает работу, но, например, делегат приложения все еще удерживает этот объект, вы все равно уверены, что произойдет выход из системы.

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

То же самое относится и к тем случаям, когда у вас есть некоторые данные в памяти, которые вы хотите записать перед выходом (например, именно это делает NSUserDefaults, чтобы обеспечить быстрый доступ к настройкам, но он все равно записывается на диск перед выходом).

Но такие случаи действительно редки. Обычно NSDocument звонит вам для записи на диск, когда это необходимо, и обычно серверы замечают, когда соединение обрывается, и очищаются самостоятельно. Таким образом, вы действительно не должны выпускать вещи, которые не принадлежат вам. Вы просто вызовете сбои при выходе для некоторых из ваших пользователей. Это создает действительно плохое впечатление и может привести к тому, что ваше приложение действительно выйдет из строя до того, как у него появится возможность сохранить некоторые данные, что ухудшит ситуацию.

Другие вопросы по тегам