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

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

Используйте методы доступа сделать управление памятью проще

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

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

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

@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;

Свойство объявляет два метода доступа. Как правило, необходимо попросить, чтобы компилятор синтезировал методы; однако, это поучительно, чтобы видеть, как они могли бы быть реализованы.

В «получить» средстве доступа Вы просто возвращаете синтезируемую переменную экземпляра, таким образом, нет никакой потребности в retain или release:

- (NSNumber *)count {
    return _count;
}

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

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

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

Предположим, что Вы хотите реализовать метод для сброса счетчика. У Вас есть несколько выбора. Первая реализация создает NSNumber экземпляр с alloc, таким образом, Вы балансируете это с a 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];
}

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

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

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [_count release];
    _count = zero;
}

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

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

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

- 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;
}

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

- (void)dealloc {
    [_count release];
    [super dealloc];
}

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

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

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

Рисунок 1  иллюстрация циклических ссылок
An illustration of retain cycles

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

Для сохранения графа объектов в целости, однако, должны быть сильные ссылки где-нибудь (если бы были только слабые ссылки, то страницы и абзацы не могли бы иметь никаких владельцев и так будут освобождены). Какао устанавливает соглашение, поэтому, что «родительский» объект должен поддержать сильные ссылки своим «дочерним элементам», и что у дочерних элементов должны быть слабые ссылки на их родителей.

Так, на рисунке 1 объект документа имеет сильную ссылку к (сохраняет) ее объекты страницы, но объект страницы имеет слабую ссылку на (не сохраняет), объект документа.

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

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

Избегите вызывать освобождение объектов, которые Вы Используете

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

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

  1. Когда объект удален из одного из фундаментальных классов набора.

    heisenObject = [array objectAtIndex:n];
    [array removeObjectAtIndex:n];
    // heisenObject could now be invalid.

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

  2. Когда освобожден «родительский объект».

    id parent = <#create a parent object#>;
    // ...
    heisenObject = [parent child] ;
    [parent release]; // Or, for example: self.parent = nil;
    // heisenObject could now be invalid.

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

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

heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];

Не Используйте dealloc для Управления Дефицитными ресурсами

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

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

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

  1. Зависимости от порядка от разрушения графа объектов.

    Механизм разрушения графа объектов по сути неупорядочивается. Несмотря на то, что Вы могли бы обычно ожидать — и добираться — определенный порядок, Вы представляете хрупкость. Если объект неожиданно автовыпущен, а не выпущен, например, порядок разрушения может измениться, который может привести к неожиданным результатам.

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

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

  3. Логика очистки, выполняемая на неправильном потоке.

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

Наборы, собственные объекты, они содержат

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

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
    [array addObject:convenienceNumber];
}

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

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
    [array addObject:allocedNumber];
    [allocedNumber release];
}

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

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

Политика владения проводится Используя, сохраняют количества

Политика владения проводится посредством подсчета ссылок — обычно вызванный, “сохраняют количество” после retain метод. Каждый объект имеет сохранить количество.