Программирование в среде Cocoa. Немного об NSAutoreleasePool

Сперва немного информации. Неоднократно я замечал топики для начищающих программистов iOS в которых был разобран вопрос управления памятью в среде Cocoa. Я хотел бы подробнее остановиться на autorelease pool. Все дело в том что программируя под iOS Cocoa предоставляет autorelease pool по умолчанию но как быть с периферийными потоками? Ведь текущий пул находится в главном потоке как же быть с autorelease объектами в бекграунде? Об этом речь пойдет далее.
Дело том что на уровне runtime cocoa предоставляет целый стек пулов. То есть при создании объекта класса NSAutoreleasePool он автоматически попадает в стек пулов и становится текущим (все объекты которым послано сообщение autorelease с момента создания пула и до его удаления попадают именно в него). Соответственно при удалении пула он автоматически удаляется из стека и удаляет все объекты находящиеся в нем. Ниже приведу листинг примера типичной ошибки программирования приводящей к утечке памяти. Предполагается что метод — (void)loadData вызван в главном потоке.



— (void)loadData
{
NSString *strURL = [NSString stringWithFormat:@«%@»,@«habrahabr.ru»];

[self performSelectorInBackground:@selector(_startLoading) withObject:strURL];
}

— (void)_startLoading:(NSString *)stringURL
{
NSURL *url = [NSURL URLWithString:stringURL];


}



Что же случится в данном случае?
Объект strURL в методе — (void)loadData успешно добавится в текущий autorelease pool так как метод вызван в главном потоке. Метод же — (void)_startLoading:(NSString *)stringURL будет вызван в периферийном потоке и autorelease pool для него создан не будет. Благо Apple позаботились о программистах и случае отсутствия текущего пула в стеке (для периферийного потока стек изначально пуст) в консоль будет выведено сообщение вроде:

*** __NSAutoreleaseNoPool(): Object 0x5b25100 of class NSURL autoreleased with no pool in place — just leaking

Это означает что был создан объект и была попытка поместить его в текущий autorelease pool по причине отсутствия такового объект никогда не будет удален — это утечка памяти. Решается это двумя путями путями:

  1. Полный переход на ручное управление памятью
  2. Создание собственного autorelease pool

На втором остановлюсь подробно. Ниже переведен листинг в котором предотвращена утечка памяти:



— (void)loadData
{
NSString *strURL = [NSString stringWithFormat:@«%@»,@«habrahabr.ru»];

[self performSelectorInBackground:@selector(_startLoading) withObject:strURL];
}

— (void)_startLoading:(NSString *)stringURL
{
NSAutoreleasePool *pool = [[NSAutorelease alloc] init];

NSURL *url = [NSURL URLWithString:stringURL];



[pool release];
}



В методе — (void)_startLoading:(NSString *)stringURL был создан autorelease pool который удаляется по окончании метода и соответственно удаляет все объекты в него занесенные.

Бывает что в консоль выводится сообщение вот такого типа:

-[NSAutoreleasePool release]: This pool has already been released, do not drain it (double release).
*** attempt to pop an unknown autorelease pool (0x681e600)

Оно означает попытку двойного удаления пула (2 раза вызван метод release), что однако не приводит к crash в debug сборке. Таких ситуаций следует избегать ибо в release сборке это может привести к тяжело отлавливаемому падению приложения!

Из личного опыта хочу дать совет новичкам — старайтесь как можно меньше использовать autorelease pool. Избегайте его использования где это возможно. Если его использование все же не обходимо то нужно быть предельно внимательным ибо объемы памяти занимаемые, например, не удаленными строками бывают очень велики!


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.