понедельник, 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х простых правил, как правило, ведет к утечкам памяти или частым крэшам приложения. В следующей статье я опишу методы борьбы с утечками и крэшами связанными с не корректной работой с памятью.

четверг, 3 сентября 2009 г.

iPhone static library

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

В мире *nix систем да и вообще в мире OC существует 2 вида библиотек.

  1. динамические;
  2. статические.

Каждый тип библиотек имеет свои достоинства и не достатки. Прозрачный механизм создания динамических библиотек под iPhone отсутствует, хотя лично мое мнение – я не вижу большого смысла в использовании их под iPhon'ом. Нам же, наибольший интерес, представляют статические библиотеки, речь о которых пойдет далее в статье.

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

В nix системах, как правило, статические библиотеки имеют расширение *.a. Таким образом, статическая библиотека – это архив, содержащий в себе объектные файлы.

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

ar -xимя_статической_библиотеки

В результатет, в том же каталоге, у вас должны появиться распакованные объектные файлы.

Итак, перейдем к практике. Практиковаться мы будем, на свежесозданном проекте и исходниках обертки над sqlite – FMDatabase.

  1. Для этого создадим новый проект, к примеру Window-based Application, (тип проекта может быть любой) и назовем его MakeStaticLib. Далее, для удобства, создадим новую группу для обертки, привяжем ее к папке и добавим в нее исходники.
  2. Нажмем правой кнопкой, на группу Classes и выберем пункт AddNew group. Свежесозданную группу назовем FMDatabase и создадим для нее одноименную папку. Для этого зайдем в свойства группы FMDatabase, нажмем на кнопку Choose и выберем папку, где мы хотим хранить исходники.
  3. Добавим исходники FMDatabase в проект, (взять их можно с сайта http://code.google.com/p/flycode/)
  4. На этом подготовительная часть окончена, теперь приступим непосредственно к созданию библиотеки. Для этого нажмем правой кнопокй на раздел Targets и выберем пункт AddNew Target.
  5. В появившемся окне выберем тип target'а Static Library, назовем ее FMDatabase и нажмем Finish. В результате структура проекта должна получиться следующая:
  6. Теперь приступим непосредственно к добавлению исходников к статической библиотеки. Здесь прячется небольшая хитрость. Дело в том, что при добавлении исходников в проект, по умолчанию они добавляются в главный таргет, а он у нас является MakeStaticLib. На простороах сети, в основном, я встречал способ, при котром файлы добавлялись напрямую в Static lib target, но к сожалению с первого раза я не смог повторить этот трюк и нашел другой, на мой взгляд, более красивый способ. Для этого выберем ТОЛЬКО *.m файлы, которые мы хотим, чтоб присутствовали в библиотеке, выбрем у них пункт Get Info, перейдем на вкладку Targets и сменим “пункт назначения” c MakeStaticLib на FMDatabase:
  7. Далее добавим библиотеку в наш проект. Для этого мы зайдем в свойства таргета MakeStaticLib на вкладку General, и выберем один из 2х способов. Первый способ заключается в том, что мы добавим библиотеку как Direct dependencies объект. Это дает возможность, после изменения кода библиотеки, автоматически перекомпилировать статическую библиотеку во перекомпиляции всего проекта. Иначе, если вы вебрете второй способ, добавления библиотеки как Linked Libriries объект, вам будет необходим сперва перекомпилировать библиотеку вручную, а потом сам проект. На мой взгял это не совсем удобно и я бы вам порекомендовал добавлять как Direct dependencies объект.
  8. И наконец последний пункт, который многие не понимают зачем делать, так как и без него все работает. Для того, чтобы в библиотеки нормально отрабатывали возможности языка Objective C (к примеру категории) необходимо добаить флаг -ObjC к флгам линковщика (Other linker flags). Делается это в Info таргета MakeStaticLib, на вкладке Build в секции Linking.

На этом создание статической библиотеки окончено.