Программирование на Objective-C 2.0
Шрифт:
В операторе NSMutableString *myStr3 = [NSMutableString stringWithString: @"string 3"];
переменной myStr3 присваивается строка, полученная из копии константной символьной строки @"string 3". Мы создали копию этой строки, поскольку классу NSMutableString было передано сообщение stringWithString:, указывающее, что содержимое строки может быть изменено в ходе выполнения программы. А по-скольку содержимое константных символьных строк нельзя изменить, система не может сделать так, чтобы переменная myStr3 только указывала на константную строку @"string 3", как это было сделано в случае myStr2.
Поэтому строковый объект myStr3 действительно имеет счетчик ссылок, что подтверждается результатами вывода. Счетчик ссылок можно изменить путем добавления этой строки к массиву или передачи ему сообщения retain, что под-тверждается результатами вывода с помощью последних двух вызовов NSLog. Метод Foundation stringWithString: добавил этот объект в autorelease-пул при его создании. Метод Foundation array
Прежде чем высвободить сам autorelease-пул, высвобождается myStr3. В ре-зультате его счетчик ссылок уменьшается до 2. Высвобождение autorelease-пула уменьшает счетчик ссылок этого объекта до 0, что приводит к освобождению занятой им памяти. Как это происходит? При высвобождении autorelease-пула каждый из объектов этого пула получает сообщение release, и это сообщение передается объекту столько раз, сколько было передано сообщений airtorelease. Поскольку строковый объект myStr3 был добавлен в autorelease-пул при создании этого объекта с помощью метода stringWithString:, ему передается сообщение release. Это уменьшает его счетчик ссылок до 1. При высвобождении массива в autorelease-пуле также происходит высвобождение каждого из его элементов. Поэтому при высвобождении массива туАл из пула каждому из его элементов (включая myStr3) передается сообщение release. "Эго уменьшает его счетчик ссы-лок до 0, в результате чего его память должна быть освобождена.
Следите, чтобы не было лишних высвобождений объекта. Если в программе 17.2 сделать счетчик ссылок mystr3 меньше 2 до высвобождения самого пула, то пул будет содержать ссылку на неверный объект. Затем при высвобождении самого пула ссылка на неверный объект вызовет, скорее всего, аварийное за-вершение программы с ошибкой неверной сегментации (segmentation fault). Подсчет ссылок и переменные экземпляра
Счетчикам ссылок необходимо уделять внимание при работе с переменными экземпляра. Вспомним метод setName: из класса AddressCard. -(void) setName: (NSString *) theName { [name release]; name = [[NSString alloc] initWithString: theName]; }
Предположим, что вместо этого мы определили setName: следующим образом и он не получил владения своим объектом name. -(void) setName: (NSString *) theName ( name = theName; }
Эта версия метода получает строку, представляющую имя человека, и со-храняет ее в переменной экземпляра name. Казалось бы, здесь все очевидно, но рассмотрим следующий вызов метода. NSString *newName; [myCard setName: newName];
Предположим, что newName — это пространство временного хранения для имени человека, которого добавили в адресную карточку, и что в дальнейшем эго пространство нужно освободить. Как вы думаете, что произойдет с переменной экземпляра name в myCard? Ее поле name будет недействительным, поскольку будет ссылаться на объект, который был ликвидирован. Именно поэтому нужно, чтобы наши классы имели свои собственные объекты-члены: эти объекты могут быть неожиданно высвобождены или модифицированы.
В следующих примерах этот вопрос обсуждается более подробно. Начнем с определения нового класса ClassA, содержащего одну переменную экземпляра: строковый объект с именем str. Напишем метод-установщик и метод-получатель (setter и getter) для этой переменной. Мы не будем синтезировать эти методы, а напишем их сами, чтобы было ясно, что происходит. // Знакомство с подсчетом ссылок #import <Foundation/NSObject.h> #import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSString.h> @interface ClassA: NSObject { NSString *str; } -(void) setStr: (NSString *) s; -(NSString *) sir; @end @implementation ClassA -(void) setStr: (NSString *) s { str = s; } -{NSString *) str { return str; } @end int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSMutableString *myStr = [NSMutableString stringWithString: @"A string"]; ClassA *myA = [[ClassA alloc] init]; NSLog (@nmyStr retain count: %x", [myStr retainCount]); [myA setStr: myStr]; NSLog (@"myStr retain count: %x", [myStr retainCount]); [myA release]; [pool drain]; return 0; }
Вывод программы 17.3 myStr retain count: 1 (счетчик ссылок myStr) myStr retain count: 1
Программа просто выделяет память (alloc) для объекта класса ClassA с именем myA и затем вызывает метод-установщик (setStr), чтобы присвоить ему объект NSString, указанный myStr. Счетчик ссылок для myStr равен 1 как до, так и после вызова метода setStr (как и следовало ожидать), поскольку этот метод просто сохраняет значение своего аргумента а своей переменной str. И здесь снова, если программа высвободит myStr после вызова метода setStr, значение, сохраненное внутри переменной экземпляра str, будет неверным, поскольку ее счетчик ссылок будет уменьшен до 0 и пространство памяти, занятое объектом, на который она ссылается, будет освобождено.
Это происходит в программе 17.3 при высвобождении autorelease-пула. Мы не добавляли строковый объект myStr в этот пул явным образом, но он был добавлен в autorelease-пул при его создании с помощью метода stringWitliString:. При высвобождении пула произошло также высвобождение myStr. Поэтому любая попытка доступа к этому объекту после
высвобождения пула будет неверной.В программе 17.4 внесены изменения в метод setStr:, чтобы удержать (retain) значение str. Это защитит от возможности случайно высвободить ссылки на объект str. // Удержание объектов #import <Foundation/NSObject.h> #import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSString.h> #import <Foundation/NSArray.h> @interface ClassA: NSObject { NSString *str; } -{void} setStr: (NSString *) s; -(NSString *) str; @end @implementation ClassA -(void) setStr: (NSString *) s { str = s; [str retain]; } -(NSString *) str { return str; } @end int main (int arge, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString * myStr = [NSMutableString stringWitliString: @"A string"]; ClassA *myA = [[ClassA alloc] init]; NSLog (@''myStr retain count: %x", [myStr retainCount]); [myA setStr: myStr]; NSLog (@nmyStr retain count: %x", [myStr retainCount]); [myStr release]; NSLog (@"myStr retain count: %x", [myStr retainCount]); [myA release]; [pool drain]; return 0; }
Вывод программы 17.4 myStr retain count: 1 myStr retain count: 2 myStr retain count: 1
Мы видим, что после вызова метода setStr: счетчик ссылок для myStr увели-чился до 2, что позволило решить эту проблему. Последующее высвобождение myStr в этой программе оставляет допустимой ссылку на нее через переменную экземпляра, поскольку ее счетчик ссылок пока равен 1.
Поскольку мы выделили память для myA с помощью alloc, мы по-прежнему обязаны высвободить ее сами. Но вместо этого мы могли бы добавить ее в autorelease-пул, отправив ей сообщение autorelease. [myA autorelease];
Это можно сделать сразу после выделения памяти для объекта. Напомним, что добавление объекта в autorelease-пул не высвобождает его и не делает его недействительным; он просто помечается для дальнейшего высвобождения. Мы можем продолжать использовать этот объект, пока не будет освобождена зани-маемая им память, что происходит при высвобождении пула, если счетчик ссылок этого объекта на этот момент стал равным 0.
У нас все еще остаются некоторые потенциальные проблемы, которые вы, возможно, видите. Метод setStr: выполняет необходимую работу по удержанию (retain) строкового объекта, который он получает в качестве своего аргумента, но когда высвобождается этот строковый объект? И что происходит со старым значением переменной экземпляра str, которое мы перезаписываем? Нужно ли высвобождать это значение, чтобы освободить занимаемую им память? В прог-рамме 17.5 содержится решение этой проблемы. // Знакомство с подсчетом ссылок #import <Foundation/NSObject.h> #import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSString.h> #import <Foundation/NSArray.h> @interface ClassA: NSObject { NSString *str; } -(void) setStr: (NSString *) s; -(NSString *) str; -(void) dealloc; @end @implementation ClassA -(void) setStr: (NSString *) s { // высвобождение старого объекта, поскольку мы закончили работать с ним [str autorelease]; // удержание (retain) аргумента на тот случай, если кто-то высвободит его str = [s retain]; -(NSString *) str { return str; ) -(void) dealloc { NSLog (@"ClassA dealloc"); [str release]; [super dealloc]; } @end int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString *myStr = [NSMutableString stringWithString: @"A string"]; ClassA *myA = [[ClassA alloc] init]; NSLog (@"myStr retain count: %x", [myStr retainCount]); [myA autorelease]; [myA setStr: myStr]; NSLog {@"myStr retain count: %x’\ [myStr retainCount]); [pool drain]; return 0; }
Вывод программы 17.5 myStr retain count: 1 myStr retain count: 2 ClassA dealloc
Метод setStr: берет то, что сохранено на данный момент в переменной эк-земпляра str, и применяет к нему autorelease, то есть делает его доступным для дальнейшего высвобождения. Это важно, если метод вызывается много раз, чтобы присваивать одному и тому же полю различные значения. Каждый раз перед сохранением нового значения старое значение должно быть помечено для высвобождения. После высвобождения старого значения новое значение удерживается (retain) и сохраняется в поле str. В выражении с сообщением str - [s retain];
используется тот факт, что метод retain возвращает своего получателя.
Примечание. Если переменная str имеет значение nil, это не представляет про-блемы. Среда выполнения Objective-C инициализирует все переменные экзем-пляра, присваивая им nil, и вполне допустимо передать сообщение nil.
Метод dealloc уже встречался в главе 15 при работе с классами AddressBook и AddressCard. Замещающий метод dealloc — это удобный способ избавиться от последнего объекта, на который ссылается наша переменная экземпляра str при освобождении ее памяти (то есть когда ее счетчик ссылок стал равным 0). В таком случае система вызывает метод dealloc, который наследуется из NSObject и его обычно не требуется замещать. Если внутри методов происходит удержание (retain) объектов, выделение для них памяти (с помощью alloc) или их копирование (с помощью методов копирования, описанных в следующей главе), может потребоваться замещение dealloc, чтобы иметь возможность их высвобождения. Операторы [str release]; [super dealloc];