Pull to refresh

Об утечках памяти в iOS и методах борьбы с ними

Reading time3 min
Views6.9K
Среди проектов, над которыми мы работаем, пожалуй, нет ни одного, в котором было бы всё в порядке с памятью. Этот пост я посвящу рассказу о том, как бороться с данным видом проблем.

К проблемам с памятью можно отнести утечки и так называемых «зомби». Приложение, в котором творится бардак с памятью, становится нестабильным, в нём появляется целый букет странностей, начинаяя с пропадания некоторых элементов интерфейса и заканчивая падениями.

Для начала давайте определимся, что такое утечки. Утечки — выделенные, но не освобождённые области памяти, на которые нет указателя. Всё это выглядит достаточно несерьёзно, у iPhone куча оперативной памяти, а обычное приложение занимает в лучшем случае 2 Мб. На деле всё не так радужно: приложение может использовать до 3мб, а дальше начинают возникать memory warning, соответственно, ОС пытается освободить те области, которые считает «ненужными». Попросту говоря, удаляет невидимые в данный момент элементы интерфейса, а это не только некрасиво, но и может привести к падению приложения. «Зомби» — уже освобождённые (убитые) области памяти, к которым пытается обратиться программа, что также приводит к падению приложения.

В iOS есть сборщик мусора!


Многие (да я и сам этим грешу) часто говорят, что его нет. Но всё же сборщик мусора есть, хоть и весьма примитивный. Принцип управления памятью основан на подсчёте количества ссылок на объект. При выделении памяти под объект их количество устанавливается равным 1, а при достижении 0 память освобождается (выбрасывается мусор). Каждый раз, когда вы хотите использовать объект, вы должны увеличить количество ссылок, а каждый раз, когда он вам становится ненужным — уменьшить.

Есть 4 волшебных слова


Чтобы управлять памятью, нужно запомнить всего 4 слова: alloc, copy, retain, release. Давайте поговорим о каждом из них в отдельности:

alloc
Выделяет память под объект и увеличивает кол-во ссылок на 1. Использовать его необходимо каждый раз, когда вы создаёте новый объект.

copy
Копирует объект (не указатель). Оставляет неизменным количество ссылок на объект, который мы копируем и увеличивает их количество на новый объект.

retain
Просто увеличивает количество ссылок на объект.

release
Обратная трём вышеперечисленным операция, уменьшает количество ссылок на 1.

И еще кое что


Autorelease
Это немного другая история. Если отправить объекту сообщение autorelease, то произойдёт «отложенный» release. Это нужно для тех случаев, когда вы не сможете по каким либо причинам отправить сообщение release объекту после действий с ним. Его основное применение — возвращение объектов методами. Любой метод, в названии которого нет alloc, copy, retain, возвращает и должен возвращать «авторелизный» объект.

Но почему бы тогда не использовать его везде? Это же так удобно! Удобно, но накладно. Из-за того, что количество ссылок на объект уменьшается не сразу, соответственно, память тоже освобождается не сразу. Тут нас и ждёт подводный камень: у нас всего 3мб, а если мы за раз обрабатываем большой объём данных, появляются те самые memory warning.

Dealloc
Этот метод вызывается при удалении объекта, в нём вы должны сделать release всех переменных, которые использовал объект, и в конце вызвать dealloc у родителя.
- (void)dealloc {
[model release];
[view release];
[super dealloc];
}

Property
Свойства бывают трёх видов: assign, retain, copy. Последние два должны быть уже понятны. А первый просто присваивает значение, никак не влияя на количество ссылок на объект. Приведу правильную реализацию сеттеров для каждого типа:

//assign
- (void)setList:(List *)list{
_list = list;
}

//retain
- (void)setList:(List *)list{
[_list autorelease];
_list = [list retain];
}

//copy
- (void)setList:(List *)list{
[_list autorelease];
_list = [list copy];
}

Помните, вам не нужно следить за тем, что делает Property, вам достаточно уcтановить его, а при необходимости сбросить значение, просто обнулить его.

Delegate, курица и яйцо


В процессе работы мы наткнулись на проблему, связанную с тем, что у нас утекало большое число объектов. Выяснилось, что у нас два объекта ретейнили друг-друга и, соответственно, никогда не освобождали память. Это происходило при использовании паттерна делегирования. Был объект, который создавал другой объект и ставил себя его делегатом. А свойство Delegate было прописанно как retain. Ситуацию спасает замена retain на assign.

Несколько советов напоследок


  • Слово release должно приходить сразу после alloc, retain или copy
  • А слово nil сразу после release
  • Всегда обnilяйте делегатов
  • По возможности воздержитесь от autorelease
  • Создавайте объекты именно тогда, когда они вам нужны
  • Не злоупотребляйте полями класса
  • Static Analysys Tool всё красиво и понятно рисует
  • Изредка проверяйте на утечки


Иван Ушаков, программист команды iOS
Tags:
Hubs:
Total votes 15: ↑8 and ↓7+1
Comments2

Articles

Information

Website
skript.sh
Registered
Founded
Employees
31–50 employees
Location
Россия