понедельник, 21 сентября 2009 г.

Memory management

Одной из главных проблем, с которой сталкивается начинающий разработчик под iPhone, является не понимание принципов управления памятью в Cocoa, которым и посвящается данная статья.


Итак, начнем с самого начала. Как нам известно, в Objective C все строится вокруг динамических объектов. Это ведет к более рациональному использованию памяти, так как мы управляем, когда освободить объект и занимаемую им память. Из этого следует, что у каждого объекта должен быть свой владелец, от этого будет зависеть, кто должен освободить объект. Каким образом можно определить, кто является владельцем объекта? В Cocoa есть простое правило – владельцем объекта является тот, кто создал его. То есть, если вы не посылали объекту сообщение содержащее alloc, copy или new, то можете не переживать, вы не владелец объекта и вам не надо заботиться об освобождении его. Иначе прийдется считать ссылки, увеличивать, уменьшать их количество… Рассмотрим пример:

Это не правильно поведение, так как функция print не являетесь владельцем объекта strPrintText. Исправим пример:

Что же значит увеличивать уменьшать количество ссылок? В Objective C управление памятью основывается на подсчете ссылок. Все классы в иерархии наследования имеют общего предка — класс NSObject. NSObject содержит в себе счетчик ссылок (retaintCount). И методы позволяющие управлять счетчиком. При создании объекта, переменной retainCount будет присвоено значение 1 и объект будет существовать до тех пор, пока retainCount не станет равен нулю. Как только это случится, объекту будет послано сообщение dealloc и объект удалится. Для увеличения/уменьшения количества ссылок в NSObject'e есть 3 метода reatin, release и autorelease. И еще 2, которые не посредственно влияют на счетчик: alloc и copy. Для того, чтоб увеличить счетчик ссылок на 1 объекту необходимо послать сообщение retain, для уменьшения – release. Есть еще метод autorelese, который уменьшает количество ссылок на 1, но через время.


Из вышесказанного можно выделить несколько простых правил по управлению памятью:

  • только владелец объекта должен освобождать объект;
  • сообщение release должно посылается объекту столько раз, сколько раз были посланы ему сообщения retain, alloc, new или copy.

На мой взгляд, наибольшей неразберихи в этом простом процессе вводят авторелизные объекты. И по сему хотелось остановиться на них поподробнее.


Как говорилось выше авторелизный объект — это объект, которому было послано сообщение autorelease и у которого количество ссылок уменьшится на 1 спустя какое то время. Для осуществления этого механизма в Cocoa предусмотрен менеджер авторелизных объектов - NSAutoreleasePool. Образно говоря пул представляет из себя стэк, хранящий в себе ссылки на авторелизные объекты. После посылки объекту сообщения autorelease, ссылка на объект копируется в ближайший пул и находится там до тех пор, пока пул не будет освобожден или ему не будет послано сообщение drain. В этом случае всем объектам находящимся в пуле будет послано сообщение release, причем столько раз, сколько им было послано сообщение autorelease. Так что будьте аккуратней. Вся хитрость использования авторелизных объектов заключается в том, что вы наверняка не знаете когда они освободятся. В Cocoa приложении существует как минимум 1 пул. В этом вы можете убедиться взглянув в файл main.m вашего проекта. Но кроме этого Application Kit автоматически создает свой пул на каждый новый поток или, к примеру, обработчик события, так что в какой именно пул попадет объект и когда он освободится вы точно не знаете(Хочу заметить, что если поток создаете вы, то вы обязаны сами создать пул).


Очень часто бывает, что написав код, протестировав его, он работает прекрасно, а потом неожиданно, без видимой причины приложение валится. На 90% - это пул пытается освободить уже освобожденный обект. Так что можете смело начинать считать ссылки. Кроме того, чрезмерное использование авторелизных объектов ведет к переполнению памяти мусором, а позже и к крэшу приложения. Прочитав выше написанное получается, что авторелизный пул лучше не использовать ну или по крайней мере свести его применение к минимуму. Лично я поддерживаю это мнение, но как показывает практика, без него никуда не деться.


Когда же использовать авторелизные объекты? Давайте рассмотрим заведомо не правильный пример:

Так делать не правильно, так как функция print не является владельцем объекта string. Память под объект string была выделена в фунцкии getString, значит там и должна быть освобождена. Исправим пример:

То есть, можно сформировать следующее правило:

  • используйте авторелизные объекты только тогда, когда вам их необходимо передать в качестве возвращаемого значения функции.

А далее или увеличивайте количество ссылок или копируйте объект.


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

Комментариев нет:

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