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

Practical Memory Management


Содержание:

— Основы

— Простые примеры

— Использование методов доступа

— Случаи, которые часто вызывают затруднения

Основы

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

— Объект может иметь одного или нескольких владельцев.

— Когда объект не имеет владельцев, он уничтожается.

— Для того, чтобы гарантировать, что интересующий вас объект не будет уничтожен, вам необходимо стать его владельцем.

— Для того, чтобы дать возможность объекту, в котором вы больше не заинтересованы, быть уничтоженным, вам необходимо отказаться от владения данным объектом.

Для поддержки такой модели Cocoa предоставляет механизм, называемый «подсчёт ссылок». Каждый объект имеет счётчик владельцев. Когда объект создаётся, его счётчик владельцев устанавливается в 1. Когда счётчик ссылок становится равным 0, объект уничтожается. У вас есть несколько методов для управления счётчиком владельцев:

alloc — выделяет память для размещения объекта и возвращает его со счётчиком ссылок равным 1.

copy — создаёт копию объекта и возвращает его со счётчиком ссылок равным 1. Если вы скопировали объект, то вы становитесь владельцем копии.

retain — увеличивает счётчик ссылок объекта на 1.

release — уменьшает счётчик ссылок объекта на 1.

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

— Внутри заданного блока кода, количество раз использования copy, alloc, retain должно быть равно количеству раз использования release, autorelease.

— Вы владете только теми объектами, для которых вы вызвали методы, чьё имя начинается с alloc, new или содержат copy (например, alloc, newObject, mutableCopy) или если вы посылаете объекту сообщение retain.

— Руализуйте dealloc метод, для того, чтобы освободить все экземплярные переменные, которыми вы владеете.

— Никогда не вызывайте dealloc напрямую, за исключением вызова dealloc для класса предка (super) в вашем собственном методе dealloc.

Многие классы предоставляют методы в форме +className…, такие методы вы можете использовать для создания нового экземпляра класса. Иначе их называют статический конструктор, виртуальный конструктор, фабричный метод. Такие методы создают новый экземпляр класса, инициализируют его и возвращает вам для использования. Вы не являетесь владельцем для объектов, которые созданы с помощью статического конструктора или метода доступа.

Простые примеры

Следующие простые примеры демонстрируют контраст в создании нового объекта с использованием alloc, статического конструктора и метода доступа.

Первый пример создаёт новый объект строка используя alloc. И поэтому, в дальнейшем этот объект должен быть освобождён.

— (void)printHello {NSString *string;

string = [[NSString alloc] initWithString:@»Hello»];

NSLog(string);

[string release];

}

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

— (void)printHello {NSString *string;

string = [NSString stringWithFormat:@»Hello»];

NSLog(string);

}

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

— (void)printWindowTitle {NSString *string;

string = [myWindow title];

NSLog(string);

}

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

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

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

@interface Counter : NSObject {NSNumber *count;

}

Для того, чтобы получить значение count или установить, вы определяете два метода. В методе доступа get вы просто возвращаете значение переменной count и поэтому нет необходимости вызывать retain или release:

— (NSNumber *)count {return count;

}

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

— (void)setCount:(NSNumber *)newCount {[newCount retain];

[count release];

// make the new assignment

count = newCount;

}

Так как класс Counter имеет экземплярную переменную, он должен реализовать метод dealloc, в котором отказаться от владения всех экземплярных перменных путём отправки им сообщения release, и в конечном итоге он должен вызвать метод dealloc у предка.

Реализация метода reset

Предположим вы хотите реализовать метод, который сбрасывает счётчик. У вас есть два пути. Первый, использовать статический конструктор для того, чтобы создать новый объект NSNumber и тогда вам нет необходимости в сообщениях retain или release. Обратите внимание, что в обоих случаях используется методы доступа (accessor)

— (void)reset {NSNumber *zero = [NSNumber numberWithInteger:0];

[self setCount:zero];

}

Во втором мы создаём экземпляр NSNumber при помощи alloc, поэтому нам необходимо сбалансировать его соовтветсвующим release.

— (void)reset {NSNumber *zero = [[NSNumber alloc] initWithInteger:0];

[self setCount:zero];

[zero release];

}

Общие ошибки

Не используется метод доступа (accessor)

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

— (void)reset {NSNumber *zero = [[NSNumber alloc] initWithInteger:0];

[count release];

count = zero;

}

Утечки

— (void)reset {NSNumber *zero = [[NSNumber alloc] initWithInteger:0];

[self setCount:zero];

}

Количество владельцев нового значения zero равно 1 (от alloc) и это значение не сбалансировано соответствующим вызовом release в области видимости метода reset. Поэтому новое значение никогда не будет освобождено, что приведёт к утечке памяти.

Экземпляр, владельцем которого вы не являетесь, был освобождён

— (void)reset {NSNumber *zero = [NSNumber numberWithInteger:0];

[self setCount:zero];

[zero release];

}

Статический конструктор возвращает объект, который будет автоматически освобождён (autorelease), поэтому вы не должны посылать сообщение release объекту zero. Отправляя сообщение release, вы уменьшите счётчик до 0 (т.к. так же в определённый момент будет вызван autorelease), что приведёт к освобождению объекта. И когда вы в следующий раз отправите сообщение объекту count, то сообщение будет направлено объекту, который удалён, что вызовет ошибку (обычно вы получите ошибку SIGBUS 10).

Случаи, которые часто вызывают затруднения

Когда вы добавляете объект в коллекцию, например в array, dictionary или set объект коллекции становится владельцем вашего добавленного объекта. Коллекция откажется от владения объектом как только объект будет удалён из коллекции или когда коллекция освободит (удалит) себя. Поэтому, например, если вы хотите создать массив чисел, вам следует поступить так:

NSMutableArray *array;NSUInteger i;

// …

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

NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];

[array addObject:convenienceNumber];

}

В этом примере вы не использовали alloc для создания объекта, поэтому вам нет необходимости вызывать release. Нет необходимости становиться владельцем числа (увеличивать счётчик ссылок на 1) convenienceNumber, т.к. массив (array) уже это сделал.

NSMutableArray *array;NSUInteger i;

// …

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

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

[array addObject:allocedNumber];

[allocedNumber release];

}

В данном случае вам необходимо отправить сообщение release объекту allocedNumber внтури области видимости цикла for для того, чтобы сбалансировать alloc. Так как массив то же стал владельцем (увеличил счётчик ссылок объекта allocedNumber), с помощью вызова метода addObject, то объект allocedNumber не будет освобождён до тех пор, пока им владеет массив array.


Похожие записи:

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля отмечены *