Практическое управление памятью

Cocoa

Хотя фундаментальные понятия, описанные в "Управление политикой памяти" являются простыми, есть некоторые практические шаги, которые вы можете предпринять, чтобы сделать управление памятью легче, а также в целях обеспечения надежности и прочности вашей программы, в то же время сводя к минимуму потребности в ресурсах.

Используйте методы доступа (акцессоры), чтобы сделать управление памятью проще

Если ваш класс имеет свойство, которое является объектом, вы должны убедиться, что любой объект, который устанавливается в качестве значения не освободится в то время как вы его используете. Поэтому вы должны претендовать на владение объектом, когда оно установлено. Вы также должны убедиться, что потом вы освободите объект, когда он больше не нужен.

Иногда это может показаться утомительным и педантичным, но если вы используете методы доступа последовательно, шансы возникновения проблем с управлением памятью уменьшаются значительно. Если вы используете retain и release к переменным экземпляров по всему вашему коду, вы практически наверняка делаете неправильные вещи.

Рассмотрим счетчик объекта, количество которого вы хотите установить.

@interface Counter : NSObject {

    NSNumber *_count;

}

@property (nonatomic, retain) NSNumber *count;

@end;

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

В "get" акцессоре, вы просто возвращаете переменную экземпляра, так что нет необходимости в retain или release:

- (NSNumber *)count {

    return _count;
}

В "set" методе, если все остальные играют по тем же правилам, вы должны принять новое значение счетчика, которое может быть удалено в любое время, поэтому вам придется взять на себя ответственность объектно-, отправив ему retain сообщение, чтобы убедиться что его не будет. Вы должны также отказаться от права владения на старый объект, отправив ему release сообщение. (Отправка сообщения к nil допускается в Objective-C, так что реализация все равно будет работать, если _count до сих пор не установлена.) Вы должны послать это после [newCount retain] на случай, если два и более объекта захотят, ненароком ее освободить.

- (void)setCount:(NSNumber *)newCount {

    [newCount retain];

    [_count release];

    // Make the new assignment.

    _count = newCount;

}

Использование методов доступа для установки значений свойств

Предположим, вы хотите реализовать метод для сброса счетчика. У вас есть несколько вариантов. Первая реализация создает экземпляр NSNumber с alloc, который вы балансируете release-ом.

- (void)reset {

    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];

    [self setCount:zero];

    [zero release];

}

Второй использует удобство конструктора для создания нового объекта NSNumber. Он уже существует поэтому нет необходимости в retain или release

- (void)reset {

    NSNumber *zero = [NSNumber numberWithInteger:0];

    [self setCount:zero];

}

Обратите внимание, что оба используют set метод акцессора

Следующий код почти наверняка работает правильно для простых случаев, но как заманчиво бы это не выглядело, чтобы отказаться от методов доступа, сделав это почти наверняка в будущем приведет к ошибке на определенном этапе (например, если вы забыли сохранить или освободить, или если изменится семантика управления памятью для переменной экземпляра).

- (void)reset {

    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];

    [_count release];

    _count = zero;

}

Отметим также, что если вы используете методику ключ-значение (KVO), то изменение переменной, таким путем, не совместимо с KVO.

Не используйте методы доступа в методах инициализаторов и dealloc

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

- init {

    self = [super init];

    if (self) {

        _count = [[NSNumber alloc] initWithInteger:0];

    }

    return self;

}

Чтобы позволить счетчику при инициализации принимать значение отличное от нуля, вы можете реализовать метод initWithCount: следующим образом:

- initWithCount:(NSNumber *)startingCount {

    self = [super init];

    if (self) {

        _count = [startingCount copy];

    }

    return self;

}

Так как переменная счетчика класса - экземпляр объекта, необходимо также реализовать dealloc метод. Следует отказаться от владения любыми переменными экземпляра, отправив им сообщение release, и в конечном счете он должн вызывать реализацию супер класса:

- (void)dealloc {

    [_count release];

    [super dealloc];

}

Используйте weak (слабые) ссылки чтобы избежать сохранения циклов

Сохранение объекта создает сильную ссылку на этот объект. Объект не может быть освобожден, пока все его сильные ссылки не будут освобождены. Проблема, известная как сохранность цикла, это может возникнуть, если два объекта имеют циклические ссылки, то есть, у них есть сильные ссылки друг на друга (либо непосредственно, либо через цепочку других объектов, каждый из которых имеет сильную ссылку на следующую ведущую к первой).

Рисунок 1. Иллюстрация циклических ссылок

retain cicles

Объектные отношения, показаные на рисунке, иллюстрируют потенциал сохранения цикла. Объект Document имеет объект Page для каждой страницы документа. Каждый объект Page имеет свойство, которое отслеживает, параграф документа. Если объект Document имеет сильную ссылку на Page объекта и объект Page имеет сильную ссылку на объект Document, ни один из объектов никогда не будет освобожден. Количество ссылок Document не может стать нулевым, пока объект Page не освобожден, а объект Page не будет освобожден, пока объект Document освобождается.

Решением проблемы сохранности циклов является использование weak (слабых) ссылок. Weak ссылка не владеет отношениями, в которых исходный объект сохраняет объект, к которому он имеет ссылки.

Чтобы сохранить объекты графов нетронутыми, где-то должны быть сильные ссылки (если бы были только слабые ссылки, то страницы и параграфы не имели бы владельцев и были бы освобождены). Cocoa устанавливает конвенции, поэтому, объект "родитель" должен поддерживать сильные (strong) ссылки на своих "детей", а дети должны иметь слабые (weak) ссылки на своих родителей.

Так, на рисунке 1 объект документа имеет сильную ссылку (retain) на объекты его страниц, но объект страница слабую ссылку (don't retain) на объект документа.

В качестве примера слабых ссылок в Cocoa можно привести источники данных для таблицы (data sources)

Вы должны быть осторожными относительно отправки сообщений на объекты, для которых вы имеете только слабые ссылки. Если вы посылаете сообщение объекту после того как он был освобожден, ваше сообщение потерпит крах. Вы должны четко определить условие, что объект является действительным. В большинстве случаев слабо указанный объект принимает слабую ссылку на другой объект, чтобы, как и в случае циклических ссылок, также нести ответственность за уведомление другого объекта, когда он освобождается. Например, при регистрации объекта с центром уведомлений,центра уведомления сохраняет слабую ссылку на объект и отправляет сообщение, когда соответствующие уведомления отправлены. Когда объект будет освобожден, необходимо отменить его регистрацию в центре уведомлений для прекращения каких-либо дальнейших сообщений на объект, который больше не существует. Аналогичным образом, когда объект делегата, будет освобожден, вы должны удалить ссылку делегата, отправив setDelegate: сообщение с nil в качестве аргумента для другого объекта. Эти сообщения, как правило, отправляются от метода dealloc объекта.

Не освобождайте объекты, которые используете

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

Есть редкие исключения из этого правила которые, в первую очередь, делятся на две категории.

  1. Когда объект удаляется из одной из фундаментальных коллекции классов.
    heisenObject = [array objectAtIndex:n];
    
    [array removeObjectAtIndex:n];
    
    // heisenObject теперь может быть недействительным.
    

    Когда объект удаляется из одной из фундаментальных коллекций классов, он отправляет release (а не autorelease) сообщение. Если коллекция была единственным владельцем объект удален, удаленный объект (heisenObject в примере), сразу же освобождается.

  2. Когда "родительский объект" будет освобожден.
    id parent = <#create a parent object#>;
    
    // ...
    
    heisenObject = [parent child] ;
    
    [parent release]; // или к примеру: self.parent = nil;
    
    // heisenObject теперь может быть недействительным.
    

    В некоторых ситуациях вы получаете объект из другого объекта, а затем прямо или косвенно посылаете release родительскому объекту. Если освобождение родителя заставляет его освободиться, а родитель был единственным владельцем ребенка, то ребенок (heisenObject в примере) будет освобожден в то же время (предполагается, что он отправляет release, а не autorelease сообщение в метод dealloc родителя).

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

heisenObject = [[array objectAtIndex:n] retain];

[array removeObjectAtIndex:n];

// Используем heisenObject...

[heisenObject release];

Не используйте dealloc для управления ограниченными ресурсами

Вы должны управлять как правило, не ограниченными ресурсами, такими как дескрипторы файлов, сетевые подключения, и буферы или кэш в dealloc методе. В частности, вы не должны проектировать классы так, чтобы dealloc был вызван, когда вы думаете, он будет вызван. Вызов dealloc, может быть отложен или обойден, либо из-за ошибки или из-за падения приложения.

Вместо этого, если у вас есть класс, экземпляры которого управляют скудными ресурсами, следует разрабатывать приложения, которым например, когда вам больше не нужны ресурсы вы можете сказать "очистить" в этой точке. Вы, как правило, затем освобождаете объект, и dealloc делает свое дело, но вы не будете испытывать дополнительные проблемы, если он этого не сделает.

Проблемы могут возникнуть, если вы попытаетесь скомбинировать управление ресурсами на вершине dealloc. Например:

  1. Заказ зависимостей от слетевших объектов графов.

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

  2. Не осваивайте дефицитные ресурсы.

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

  3. Логика очистки запущена на ошибочном потоке.

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

Коллекции собственных объектов

При добавлении объекта в коллекцию (например, массив, словарь, или набор), коллекция становится его владельцем. Коллекция откажется от владения объектом, когда объект удаляется из коллекции или коллекця будет освобождена. Так, например, если вы хотите создать массив чисел вы можете выполнить одно из следующих действий:

NSMutableArray *array = <#Получаем изменяемый массив#>;

NSUInteger i;

// ...

for (i = 0; i < 10; i++) {

    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];

    [array addObject:convenienceNumber];

}

В этом случае вы не вызываете alloc, так что нет необходимости вызывать release. Значит нет необходимости вызова retain для новых номеров (convenienceNumber), так как массив это сделает.

NSMutableArray *array = <#Получаем изменяемый массив#>;

NSUInteger i;

// ...

for (i = 0; i < 10; i++) {

    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger: i];

    [array addObject:allocedNumber];

    [allocedNumber release];

}

В этом случае, вам нужно отправить allocedNumber сообщение release в рамках цикла, чтобы сбалансировать alloc. Так как массив сохранил номер, когда он был добавлен addObject:, он не будет освобожден, пока он в массиве.

Чтобы понять это, поставьте себя в положение человека, который реализован как класс коллекции. Вы хотите быть уверенным, что никакие объекты, о которых Вы заботитесь не исчезнут, чтобы их сохранить вы отправляете им сообщение retain. Если они удаляются, вы отправляете сообщение release для балансировки, и все оставшиеся объекты должны направить release сообщение в свой ​​собственный метод dealloc.

Политика владения реализуется подсчетом ссылок

Политика владения реализуется через подсчет ссылок, обычно называемых "Счетчик ссылок" после метода retain. Каждый объект имеет счетчик ссылок.

  • Когда вы создаете объект, счетчик ссылок устанавливается в 1.
  • Когда вы посылаете сообщение retain, счетчик увеличивается на 1.
  • Когда вы посылаете сообщение release, счетчик уменьшается на 1.

    Когда вы посылаете объекту сообщение autorelease, его счетчик уменьшается на 1 на определенном этапе в будущем.

  • Если счетчик ссылок достиг 0, объект уничтожается
 
 
homeЗаметили ошибкукарта сайта 
   Made on a Mac