Программирование на Objective-C 2.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; }
Эта версия метода получает строку, представляющую имя человека, и
Предположим, что 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-пул не высвобождает его и не делает его недействительным; он просто помечается для дальнейшего высвобождения. Мы можем продолжать использовать этот объект, пока не будет освобождена зани-маемая им память, что происходит при высвобождении пула, если счетчик ссылок этого объекта на этот момент стал равным 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];
сначала высвобождают переменную экземпляра str и затем вызывают родитель-ский метод dealloc, чтобы закончить работу.
Вызов NSLog был помещен внутри метода dealloc, чтобы выводить сообще-ние, когда вызывается этот метод. Мы сделали это, чтобы подтвердить, что объект ClassA правильно высвобожден после высвобождения autorelease-пула.
Возможно, вы увидели один последний недочет в методе-установщике setStr. Рассмотрим еще раз программу 17.5. Предположим, что myStr является мута- бельной строкой (а не немутабельной) и один или несколько символов в myStr были изменены после вызова setStr. Изменения в строке, на которую ссылается myStr, повлияют также на строку, на которую ссылается переменная экземпляра, поскольку они ссылаются на один и тот же объект. Перечитайте последнее предложение, чтобы убедиться, что вы понимаете его. Вы должны также понять, что присваивание myStr совершенно нового строкового объект не вызовет этой проблемы. Проблема возникает только в том случае, если будут изменены каким-либо способом один или несколько символов строки.