Управление памятью в Objective C

By | November 22, 2012

При разработке софта под iPhone, столкнулся с проблемой непонимания механизма распределения памяти. Являясь java-разработчиком, привыкшим к чудесам Garbage Collector’а, тема памяти в Objective C поначалу доставляла мне массу неприятностей. Спустя некоторое время, написав свое первое приложение, я подумал, что полностью разобрался в этой непростой теме… и это было моей ошибкой. Все таки, сначала надо хорошо читать мануалы и желательно до конца :)

Назвал я данную тему непростой не из-за сложности механизмов управления памятью – они то как раз более-менее понятные и простые, а, скорее, из-за того, что не разобравшись до конца, как правильно “освободить” объект из памяти, можно получить массу сложноотлавливаемых ошибок типа EXC_BAD_ACCESS, причем необязательно именно в том месте, где была допущена ошибка распределения памяти.

Начну с того, что Objective C является основным языком программирования под Mac OS X и сама Apple рекомендует использовать для управления памятью механизм Garbage Collection. Однако, на данный момент, такой подход не доступен для разработки под iPhone. Что ж, придется заботится об удалении всех переменных из памяти вручную.

Итак, начну с базовых вещей. Управление памятью в Objective C базируется на принципе количества ссылок на объект. Когда мы создаем новый объект, его счетчик ссылок становится равным единице. Как только объект перестает быть нужным, мы говорим ему об этом и счетчик уменьшается на ту самую единицу. Как только счетчик становится равным нулю, объект автоматически удаляется из памяти. Приведу простой пример:

Первая строка данного примера вызывает поочередно alloc, который выделяет место в памяти под будущий объект и метод init, который, в свою очередь, инициализирует объект (аналог конструктора). Счетчик ссылок при этом устанавливается равным единице. Последней строкой мы говорим, что данный объект нам больше не нужен, тем самым заставляя счетчик ссылок уменьшится на 1, становясь равным нулю. Как я писал выше, нулевое кол-во ссылок незамедлительно удаляет объект из памяти, вызывая метод dealloc.

Существует также другой способ освобождения объекта – autorelease. Разница в том, что стандартный release удалит ссылку на объект незамедлительно (если счетчик стал = 0), в то время, как autorelease сделает это позже и ссылка на объект будет оставаться действительной до выхода из функции, где autorelease был вызван или до принудительной очистки NSAutoreleasePool.

Целесообразно использовать autorelease, например, в set методах, т.к. объекты str и name из примера выше могут ссылаться на один и тот же объект и если бы мы делали release, то это сразу бы удалило его из памяти (при условии, что на момент входа в setName, объект str/name имел кол-во ссылок = 1). Соответственно на строке [str retain] вылетела бы ошибка EXC_BAD_ACCESS.

Итак, базовое правило при работе с памятью, которое нужно запомнить:

Каждому alloc, new*, copy или retain должен соответствовать свой release или autorelease. Это значит, если вы создавали объект или становились его владельцем через copy/retain, то вам же необходимо его удалить из памяти. Во всех других случаях release или autorelease делать ни в коем случае нельзя.

Примерами случаев, когда вы не должны заботиться об удалении объекта из памяти может послужить любая ситуация, когда вы не вызывали вышеупомянутых alloc / copy / retain:

В данном примере мы явно не создавали новый инстанс NSDate, а следовательно нам и не надо думать о его дальнейшей судьбе. Единственное, что надо иметь в виду – объект помечен как autorelease, а значит, someObject setDate должен сделать на нем retain, т.к. он получил его в использование. Это произойдет автоматически, если вы в someObject будете использовать @property:

Другой пример:


 

Исходя из нашего базового правила мы освобождаем num1, который был явно создан, но не трогаем num2, созданием которого занимался фреймворк Foundation.

Подводя итог моего знакомства с управлением памятью в Objective C, еще раз повторю две основные вещи: всегда удаляйте из памяти объекты, которые сами создали и не забывайте делать copy или retain на получаемых в использование объектах, пока их владелец не успел освободить их.

Также, рекомендую почитать документ от Apple на эту тему:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011-SW1