Главная iPhone Mac OS X Форум О себе

Утечки памяти в iPhone. Изучаем инструмент Leaks

В последнее время активно работаю с программой Instruments. Как выяснилось, она особенно актуальна для обнаружения утечек памяти. Я подумал, что раз эта функция так полезна для меня, возможно, и другим пользователям будет интересно узнать, как с ее помощью выявлять утечки памяти.

Что такое утечка памяти и почему она так важна?

Утечка памяти происходит, когда программа утрачивает доступ к фрагменту информации, для которого выделялась динамическая память. В результате “утерянная” память больше не высвобождается. Такое обычно происходит, когда код включает функции “new“, “malloc” или “alloc” в отсутствие соответствующих им “delete“, “free” либо “release“.

При задействующихся функциях “new“, “malloc” и “alloc” операционная система выделяет программе фрагмент памяти, предлагая определенную область, находящуюся по конкретному адресу. При этом система рассчитывает, что на данный фрагмент памяти будет иметься ссылка (обычно в форме адресного указателя). По окончании работы с ним ОС ждет сообщения о последующих действиях (посредством обращения к функциям “delete“, “free” либо “release“).

Если в программе не остаётся указателей на адреса этих фрагментов, налицо утечка. Программа не знает расположения объектов в динамической памяти, соответственно, у нее нет возможности их удалить.

А почему, собственно, это должно вас интересовать? Во-первых, как минимум, утрачиваются ресурсы памяти, которые освободятся только после выхода из программы. Как максимум, утечка памяти будет происходить в каждом фрейме. Велика вероятность, что результатом станет сбой в программе, особенно если она будет запущена на достаточно длительное время.

Как узнать, что имеет место утечка памяти?

Иногда утечка легко обнаруживается простым изучением кода. Но встречаются и более сложные случаи. Вот здесь и приходит на помощь программа Instruments. Предусмотренный в ней инструмент “Leaks” точно определит место утечки, позволив ее устранить.

Тестовое приложение.

Для примера я создал приложение с утечкой памяти в двух местах: в контроллере представления Objective-C и в задействованном в приложении классе C++. Нужный код можно загрузить здесь. На примере приведенных ниже фрагментов из него мы и будем распознавать утечку памяти.

Фрагмент “InstrumentsTestViewController.mm”


— (void)viewDidLoad {
    [super viewDidLoad];

    LeakyClass* myLeakyInstance = new LeakyClass();
    delete myLeakyInstance;

    mMyLeakyString = [[NSString alloc] initWithUTF8String:"I'm a leaky string."];

    [self doSomethingNow];
}

— (void) doSomethingNow
{
    mMyLeakyString = [[NSString alloc] initWithUTF8String:
        "Look, another alloc, but no release for first one!"];
}

Фрагмент “LeakyClass.mm”.


LeakyClass::LeakyClass()
{
    mLeakedObject = new LeakedObject();
}

LeakyClass::~LeakyClass()
{
}

Создав приложение “InstrumentsTest iPhone” в режиме “Debug“, я запущу его на iPhone. (Подпись программы вам придется отредактировать под собственное устройство.) После этого запущу программу Instruments (для поиска достаточно набрать “Instruments” в поле поиска Spotlight).

Instruments.

После запуска программы Instruments указывается нужный пользователю инструмент. Слева выберите “iPhone“, а в меню справа дважды щелкните на инструменте “Leaks“.

После этого появится примерно такое окно:

Проверьте, чтобы iPhone был подключен к компьютеру. В левом верхнем углу окна находится раскрывающееся меню запуска “Launch Executable“. Щелкнув на нем, убедитесь, что в качестве активного устройства выбран iPhone (а не компьютер). В меню выводится список всех установленных на iPhone приложений. Найдите то, в котором будете работать с функцией “Leaks” (в данном случае, “InstrumentsTest“) и выделите его щелчком.

Мы готовы приступать. Щелчок на красной кнопке “Record” запускает приложение: теперь для него фиксируется каждое выделение динамической памяти. С периодичностью в 10 секунд автоматически выполняется проверка на предмет утечки.

При желании можно настроить частоту автоматической проверки утечек. Эта операция может выполняться также исключительно по команде пользователя (при проверке программа зависает на 3–5 сек, поэтому тестировать ее, одновременно выявляя утечки, в таком режиме весьма проблематично). Как правило, я устанавливаю ручной контроль, а после по мере необходимости пользуюсь кнопкой “Check for leaks” (например, после загрузки нового режима игры, после выхода из игры в основное меню и пр.). Щелкнув на строке “Leaks“, просмотрите/настройте параметры с помощью кнопки View -> Detail в верхнем правом углу экрана. В нашем примере я оставил автоматический режим.

После запуска приложения в течение нескольких секунд автоматическая проверка выявит сразу две утечки памяти. Фантастика! А что теперь с ними делать?

Окно “Extended Detail View”

Программа Instruments поступает очень хитро: никаких подсказок по поводу того, что делать теперь, она не дает. Обратите внимание на ряд кнопок в нижней части экрана. Видите ту маленькую, с двумя прямоугольниками? Наведите на нее мышь — появится надпись “Extended Detail View“. (Можно воспользоваться для этой цели командой View -> Extended Detail.)

При щелчке на кнопке открывается окно в правой части экрана с детализированной информацией по всем утечкам!

Щелкните на одной из них, и в окне “Extended Detail View” появится полная история стека вплоть до момента обнаружения утечки. В нашем примере при щелчке на первой утечке обнаруживается, что она произошла внутри “[NSString initWithUTF8String]“. При возвращении в истории стека на шаг назад выясняется, что последнее обращение внутри приложения выполнялось к “[InstrumentsTestViewController viewDidLoad]“.

Следующий момент мне особенно симпатичен: двойной щелчок на этой строке в окне “Extend Detail View” открывает XCode непосредственно в нужной точке!

В данном случае мы видим, что утечка произошла при первом выделении памяти “NSString“. Здесь и нужно копнуть глубже. Рассматриваемый случай элементарен, но даже с ним догадаться о причинах утечки будет непросто. Давайте рассмотрим наш пример подробнее.

В методе “viewDidLoad” мы выделяем динамическую память строке:


mMyLeakyString = [[NSString alloc] initWithUTF8String:"I'm a leaky string."];

В методе “dealloc” мы ее высвобождаем:


[mMyLeakyString release];

Казалось бы, утечки быть не должно. Однако давайте проанализируем код для всех ссылок на “mMyLeakyString“. Внутри “doSomethingNow:” обнаруживаем следующее:


mMyLeakyString = [[NSString alloc] initWithUTF8String:
    "Look, another alloc, but no release for first one!"];

Обратите внимание: мы выделили память для новой строки, а указателем сделали “mMyLeakyString“. Проблема заключается в том, что мы не освободили “mMyLeakyString” до того, как она стала указателем. В итоге исходная строка зависла в воздухе, и освободить эту память возможности нет. Обращение к функции “release” в методе “dealloc” на самом деле освобождает вторую строку, на которую выделялась память в рамках “doSomethingNow“, поскольку именно туда указывает ссылка.

Чтобы исправить проблему, попробуем изменить “doSomethingNow” примерно так:


— (void) doSomethingNow
{
    [mMyLeakyString release];
    mMyLeakyString = [[NSString alloc] initWithUTF8String:
        "Look, another alloc, but released first one!"];
}


“Look, another alloc, but no release for first one!”];

Тем самым мы освобождаем первую строку, на которую выделялась динамическая память, прежде чем “mMyLeakyString” становится указателем. После повторной отладки и запуска приложения в программе Instruments одной утечкой памяти становится меньше. Безусловно, есть и более оптимальные способы работы с “NSStrings“, но при таком подходе предлагаемое решение устранит проблему.

Переходим ко второй утечке. Щелкнув на строке, изучим, что же к ней привело. Находим первое обращение в приложении, и оказывается, что причина утечки — в конструкторе “LeakyClass::LeakyClass()“.

Дважды щелкните на нем в окне событий, и снова получите искомый фрагмент в XCode.

Здесь конструктор создает новый “LeakedObject“. Но что это? Деструктор так и не освободил ссылку? Нехорошо… Каждого новичка нужно своевременно удалять. Откорректируем деструктор, как показано ниже:


LeakyClass::~LeakyClass()
{
    if (mLeakedObject != NULL)
    {
        delete mLeakedObject;
        mLeakedObject = NULL;
    }
}

После отладки вновь запустите приложение в программе Instruments — утечек памяти больше нет!

На двух этих примерах, каждый из которых крайне прост, я показал, как посредством программы Instruments отслеживать утечки памяти и для объектов Objective-C, и для классов C++, интегрированных в приложение.

За работу! Пора устранить все утечки памяти. Помните, приложение без утечек — счастливое приложение!

Скачать архив (15Кб)

Оригинальная статья www.mobileorchard.com
Источник lookapp.ru

Комментарий

Комментарии

Багтрекер 10.01.2020 15:52

Спасибо, очень помогло!

ответить
Кот 22.05.2020 18:48

Спасибо! Отличный туториал!

ответить

Форма комментирования для «Утечки памяти в iPhone. Изучаем инструмент Leaks»

Обязательное поле. Не больше 30 символов.

Обязательное поле

captcha image Пожалуйста, введите символы, которые вы видите на изображении

.