Шрифт:
Это происходит в программе 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];
сначала высвобождают переменную экземпляра str и затем вызывают родитель-ский метод dealloc, чтобы закончить работу.
Вызов NSLog был помещен внутри метода dealloc, чтобы выводить сообще-ние, когда вызывается этот метод. Мы сделали это, чтобы подтвердить, что объект ClassA правильно высвобожден после высвобождения autorelease-пула.
Возможно, вы увидели один последний недочет в методе-установщике setStr. Рассмотрим еще раз программу 17.5. Предположим, что myStr является мута- бельной строкой (а не немутабельной) и один или несколько символов в myStr были изменены после вызова setStr. Изменения в строке, на которую ссылается myStr, повлияют также на строку, на которую ссылается переменная экземпляра, поскольку они ссылаются на один и тот же объект. Перечитайте последнее предложение, чтобы убедиться, что вы понимаете его. Вы должны также понять, что присваивание myStr совершенно нового строкового объект не вызовет этой проблемы. Проблема возникает только в том случае, если будут изменены каким-либо способом один или несколько символов строки.
Решением этой проблемы является создание новой копии строки внутри метода-установщика, если вы хотите защитить ее и сделать совершенно неза-висимой от аргумента этого метода. Именно поэтому здесь выбрано создание копии компонентов name и email в методах setName: и setEmail: класса AddressCard (см. главу 15). 17.3. Пример автоматического высвобождения
Рассмотрим последний пример из этой главы, чтобы убедиться, что вы действи-тельно понимаете, как действует подсчет ссылок, удержание (retain) и высво- бождение/автоматическое высвобождение (release/autorelease) объектов. Рас-смотрим программу 17.6, которая определяет фиктивный класс с именем Foo с одной переменной экземпляра и только наследуемыми методами. #import <Foundation/NSObject.h> #import <Foundation/NSAutoreleasePool.h> @interface Foo: NSObject { int x; } @end @implementation Foo @end int main (int arge, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Foo *myFoo = [[Foo alloc] init]; NSLog (@"myFoo retain count = %x", [myFoo retainCount]); [pool drain]; NSLog (@"after pool drain = %x", [myFoo retainCount]); pool = [[NSAutoreleasePool alloc] init]; [myFoo autorelease]; NSLog (@"after autorelease = %x", [myFoo retainCount]); [myFoo retain]; NSLog (@"after retain = %x", [myFoo retainCount]); [pool drain]; NSLog (@"after second pool drain = %x", [myFoo retainCount]); [myFoo release]; return 0; }
Вывод программы 17.6 myFoo retain count = 1 (счетчик ссылок myFoo) after pool drain = 1 (после pool drain) after autorelease = 1 (после autorelease) after retain = 2 (после retain) after second pool drain = 1 (после второго pool drain)
Программа выделяет память для нового объекта Foo и присваивает его пере-менной myFoo. Как вы уже видите, начальное значение ее счетчика удержаний (ссылок) равно 1. Этот объект еще не является частью autorelease-пула, поэтому высвобождение этого пула не делает объект недействительным. Затем выделяется новый пул, и myFoo добавляется в этот пул путем отправки сообщения autorelease. Снова отметим, что счетчик ссылок этой переменной не изменяется, так как добавление объекта в autorelease-пул не влияет на его счетчик ссылок, а только помечает его для дальнейшего высвобождения.