Уроки iPhone SDK: (Часть 4) Программирование игр на iPhone. “Пишем” игру iTennis. Добовляем звук. Уроки iPhone SDK: Работа с базовой анимацией на iPhone.
Май 19

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

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

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

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

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

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (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”.

1
2
3
4
5
6
7
8
9
10
11
12
13
LeakyClass::LeakyClass()

{

mLeakedObject = new LeakedObject();

}

LeakyClass::~LeakyClass()

{

}

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

Instruments.

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

instruments_01

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

instruments_02

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

instruments_03

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

instruments_04

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

instruments_05

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

instruments_05b

Окно “Extended Detail View”.

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

instruments_06

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

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

instruments_06b

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

instruments_07

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

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

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

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

1
[mMyLeakyString release];

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

1
2
3
mMyLeakyString = [[NSString alloc] initWithUTF8String:

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

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

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

1
2
3
mMyLeakyString = [[NSString alloc] initWithUTF8String:

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

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

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

instruments_08

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

instruments_09

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LeakyClass::~LeakyClass()

{

if (mLeakedObject != NULL)

{

delete mLeakedObject;

mLeakedObject = NULL;

}

}

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

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

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

Исходный код скачать можно [здесь]

Текст оригинальной статьи на английском языке [здесь]

Уважаемые читатели, данный материал был переведен и подготовлен к публикации проектом LookApp.ru, при публикации на другом сайте ссылка на LookApp.ru обязательна.

1 звезда2 звезд3 звезд4 звезд5 звезд (2 голосов, средний: 5.00 из 5)
Загрузка ... Загрузка ...


3 Responses to “Уроки iPhone SDK: Утечки памяти в iPhone. Изучаем инструментом “Leaks””

  1. 1. berec Says:

    Обнаружена утечка - но когда открываю Extended Detail View - из него нет возможности перейти к моменту в программе где произошла утечка. Поскольку там только указатели на CoreFoundation и CFNetwork. Что делать с такими утечками?

  2. 2. berec Says:

    какие еще тулзы из Инструментов могут быть полезны при тестировании iPhone приложений?

  3. 3. shableslav Says:

    Возникла проблема.
    Использование инструмента с симулятором проходит нормально, а вот когда подключаю iPhone, то получаю ошибку “target terminated too early to collect data” - вместо обоих графиков использования памяти.

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

Оставьте комментарий