Программирование на Objective-C 2.0
Шрифт:
Когда объект уже не нужен, мы уменьшаем на I его счетчик ссылок, отправляя сообщение release, как в следующей строке. [myFraction release];
Когда счетчик ссылок объект становится равным 0, система «понимает», что этот объект больше не нужен (поскольку на него нет больше ссылок), и поэтому она освобождает (deallocate) его память. Для этого объекту отправляется со-общение dealloc.
Успешное осуществление этой стратегии требует аккуратности от програм-миста, чтобы счетчик ссылок правильно наращивался и уменьшался во время выполнения программы. Как вы увидите ниже, система выполняет только часть этой работы.
Рассмотрим подсчет ссылок несколько подробнее. Объекту можно отправить сообщение retainCount, чтобы получить его счетчик ссылок (или удержаний, retain). Обычно вы не будете использовать этот метод, но здесь мы рассмотрим его в иллюстративных целях (см. программу 17.1). Отметим, что он возвращает целое без знака (unsigned int) типа NSUInteger. //
Вывод программы 17.1 mylnt retain count = 1 (счетчик удержаний mylnt) after adding to array = 2 (после добавления в массив) after asssignment to mylnt2 = 2 (после присваивания mylnt2) mylnt after retain = 3 (mylnt после удержания) mylnt2 after retain = 3 (mylnt2 после удержания) after release = 2 (после release) after removal from array = 1 (после удаления из массива)
Объекту NSNumber mylnt присваивается целое значение 100, и вывод показы-вает, что начальное число его удержаний равно 1. Затем этот объект добавляется в массив myArr с помощью метода addObject:. Обратите внимание, что после этого его счетчик ссылок равен 2. Метод addObject: делает это автоматически; в документации по addObject: описан этот факт. Добавление объекта в любой тип коллекции увеличивает его счетчик ссылок. Это означает, что когда мы высво-бождаем (release) добавленный ранее объект, на него можно будет по-прежнему ссылаться из массива, и он не будет высвобожден.
Затем мы присваиваем mylnt переменной mylnt2. Отметим, что это не приво-дит к наращиванию счетчика ссылок, что может вызвать в дальнейшем потен-циальные проблемы. Например, если счетчик ссылок для mylnt уменьшится до 0 и его память будет освобождена, mylnt2 будет содержать неверную ссылку на объект (напомним, что присваивание mylnt переменной mylnt2 не приводит к копированию самою объекта, а только к созданию указателя на место в памяти, где находится сам объект).
Поскольку mylnt теперь имеет еще одну ссылку (через mylnt2), мы наращиваем его счетчик ссылок, отправляя ему сообщение retain. Это происходит в следующей сгроке программы 17.1. Как мы видим, после отправки сообщения retain счетчик ссылок становится равным 3. Первая ссылка — это сам объект, вторая ссылка делается из массива и третья — во время присваивания. Сохранение элемента в массиве вызывает автоматическое наращивание счетчика ссылок, а присваивание другому элементу — нет, поэтому мы должны сделать это сами. Отметим, что при выводе счетчик ссылок mylnt и на mylnrt2 дает одинаковое значение 3; дело в том, что в обоих случаях это ссылка на один и тот же объект в памяти.
Предположим, что мы прекратили использовать объект mylnt в программе. Это можно сообщить системе, отправив объекту сообщение release. Как мы можем видеть, его счетчик ссылок в результате уменьшается с 3 до 2. Счетчик нс равен 0; это означает, что продолжают действовать другие ссылки (из массива и через mylnt2). Система не освобождает память, используемую этим объектом, поскольку счетчик ссылок не равен нулю.
После удаления первого элемента из массива myArr с помощью метода removeObjectAtlndex: мы видим, что счетчик ссылок автоматически уменьшился до В общем случае удаление объекта из любой коллекции сопровождается уменьшением на 1 его счетчика ссылок. Поэтому следующая последовательность кода может вызвать проблемы. mylnt = [myArr ObjectAtlndex: 0]; [myArr removeObjectAtlndex: 0]
Дело в том, что в данном случае объект, на который ссылается mylnt, может стать недействительным после вызова метода removeObjectAtlndex:, если его счет-чик ссылок уменьшился до 0. Конечно, для решения этой проблемы нужно удер-жать (retain) mylnt после считывания из массива, чтобы на его ссылку не повлияло то, что происходит в других местах. Подсчет ссылок и строки
В программе 17.2 показано, как действует подсчет ссылок для строковых объектов. // Подсчет ссылок в случае строковых объектов. #import <Foundatton/NSObject.h> #import <Foundation/NSAutoreleasePool.h> Simport <Foundation/NSString.h> #import <Foundation/N$Array.h> int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString *myStr1 = @"Constant string"; NSString *myStr2 = [NSString stringWithString: @"string 2"]; NSMutableString *myStr3 = [NSMutableString stringWithString: @"string 3"]; NSMutableArray *myArr = [NSMutableArray array]; NSLog (@"Retain count: myStrl: %lx, myStr2: %lx, myStr3: %lx", (unsigned long) [myStrl retainCount], (unsigned long) [myStr2 retainCount], (unsigned long) [myStr3 retainCount]); [myArr addObject: myStrl]; [myArr addObject: myStr2]; [myArr addObject: myStr3]; NSLog (@"Retain count: myStrl: %lx, myStr2: %lx, myStr3: %lx", (unsigned long) [myStrl retainCount], (unsigned long) [myStr2retainCount], (unsigned long) [myStr3 retainCount]); [myArr addObject: myStrl]; [myArr addObject: myStr2]; [myArr addObject: myStr3]; NSLog (@"Retain count: myStrl: %lx, myStr2: %lx, myStr3: %lx", (unsigned long) [myStrl retainCount], (unsigned long) [myStr2retainCount], (unsigned long) [myStr3 retainCount]); [myStrl retain]; [myStr2 retain]; [myStr3 retain]; NSLog (@"Retain count: myStrl: %lx, myStr2: %lx, myStr3: %lx", (unsigned long) [myStrl retainCount], (unsigned long) [myStr2 retainCount], (unsigned long) [myStr3 retainCount]); //
Вывод программы 17.2 Retain count: myStrl: ffffffff, myStr2: ffffffff, myStr3: 1 (Счетчик ссылок:) Retain count: myStrl: ffffffff, myStr2: ffffffff, myStr3: 2 Retain count: myStrl: ffffffff, myStr2: ffffffff, myStr3: 3
Объекту NSString myStrl присваивается строка NSConstantString @'Constant string" (Константная строка). Вьшеление места в памяти для константных строк отличается от других объектов. Константные строки не имеют механизма подсчета ссылок, поскольку их нельзя высвободить. Именно поэтому при отправке сообщения retainCount переменной myStrl счетчик возвращает значение Oxffffffff. (Это на самом деле максимально возможное целое значение без знака, то есть UINT_MAX в стандартном header-файле <limits.h>.)
Примечание. Очевидно, что в некоторых системах счетчик ссылок, возвращаемый для константных строк в программе 17.2, дает значение Qx7fffffif (а не Gxffffffff), что является максимально возможным целым значением со знаком, то есть INT_MAX.
Отметим, что то же самое относится к немутабельному строковому объекту, который инициализируется с константной строкой: он тоже не имеет счетчика ссылок, что подтверждается счетчиком ссылок, выведенным для myStr2.
Примечание. В данном случае система уже достаточно «сообразительна», по-этому она определила, что немутабельный строковый объект инициализируется с помощью константного строкового объекта. До выпуска Leopard такая оп-тимизация не выполнялась, и поэтому для mystr2 действовал счетчикудержаний.
В операторе NSMutableString *myStr3 = [NSMutableString stringWithString: @"string 3"];
переменной myStr3 присваивается строка, полученная из копии константной символьной строки @"string 3". Мы создали копию этой строки, поскольку классу NSMutableString было передано сообщение stringWithString:, указывающее, что содержимое строки может быть изменено в ходе выполнения программы. А по-скольку содержимое константных символьных строк нельзя изменить, система не может сделать так, чтобы переменная myStr3 только указывала на константную строку @"string 3", как это было сделано в случае myStr2.
Поэтому строковый объект myStr3 действительно имеет счетчик ссылок, что подтверждается результатами вывода. Счетчик ссылок можно изменить путем добавления этой строки к массиву или передачи ему сообщения retain, что под-тверждается результатами вывода с помощью последних двух вызовов NSLog. Метод Foundation stringWithString: добавил этот объект в autorelease-пул при его создании. Метод Foundation array также добавил массив myArr в этот пул.
Прежде чем высвободить сам autorelease-пул, высвобождается myStr3. В ре-зультате его счетчик ссылок уменьшается до 2. Высвобождение autorelease-пула уменьшает счетчик ссылок этого объекта до 0, что приводит к освобождению занятой им памяти. Как это происходит? При высвобождении autorelease-пула каждый из объектов этого пула получает сообщение release, и это сообщение передается объекту столько раз, сколько было передано сообщений airtorelease. Поскольку строковый объект myStr3 был добавлен в autorelease-пул при создании этого объекта с помощью метода stringWithString:, ему передается сообщение release. Это уменьшает его счетчик ссылок до 1. При высвобождении массива в autorelease-пуле также происходит высвобождение каждого из его элементов. Поэтому при высвобождении массива туАл из пула каждому из его элементов (включая myStr3) передается сообщение release. "Эго уменьшает его счетчик ссы-лок до 0, в результате чего его память должна быть освобождена.