Программирование на Objective-C 2.0
Шрифт:
значение myPoint передается этому методу как аргумент. Это значение указывает место в памяти, где хранится данный объект XYPoint (рис. 8.5).
Рис. 8.5. Объект myPoint класса XYPoint в памяти
Это значение, сохраненное в myPoint и являющееся указателем места в памяти, копируется в локальную переменную pt, определенную внутри метода. После этого pt и myPoint являются ссылкой на одни и те же данные, хранящиеся в памяти (рис. 8.6).
Рис. 8.6. Передача методу
Когда переменной origin присваивается pt внутри этого метода, указатель, хранящийся внутри pt, копируется в переменную экземпляра origin (рис. 8.7).
Рис. 8.7. Задание начала (origin) прямоугольника
Поскольку myPoint и переменная origin, хранящаяся в myRect, ссылаются на одну и ту же область в памяти (как и локальная переменная pt), при последующем изменении значения myPoint на (50,50) изменяется и значение начала прямоугольника.
Чтобы избежать этой проблемы, нужно модифицировать метод setOrigin: так, чтобы он выделял (alloc) свою собственную точку и присваивал началу прямоугольника (origin) эту точку. -(void) setOrigin: (XYPoint *) pt { origin = [[XYPoint alloc] init]; [origin setX: pt.x andY: pt.y]; }
Метод сначала выделяет память и инициализирует новый объект класса XYPoint. В выражении для сообщения [origin setX: pt.x andY: pt.y];
новому объекту класса XYPoint присваивается значение координат х,у аргумента, передаваемого методу.
Это изменение в методе setOrigin: означает, что теперь каждый экземпляр Rectangle владеет свои собственным экземпляром XYPoint. Теперь он не только осуществляет выделение памяти для XYPoint, но и освобождает эту память. Если класс содержит другие объекты, бывает нужно, чтобы он владел некоторыми или всеми объектами. Для прямоугольника класс Rectangle должен владеть объектом начала (origin) прямоугольника, поскольку это один из основных атрибутов.
Но была ли освобождена память, которая использовалась для origin? Освобождение памяти, занятой для прямоугольника (myRect), не освобождает память, которая была выделена для начала прямоугольника (origin). Чтобы освободить эту память, нужно вставить в main строку [[myRect origin] release];
В результате будет освобожден объект XYPoint, возвращаемый методом origin. Вы должны сделать это до того, как освободите память для самого объекта Rectangle, поскольку ни одна из переменных, содержащихся в этом объекте, недействительна после того, как освобождена память объекта. Необходима следующая последовательность строк кода. [[myRect origin] release]; // Освобождение памяти для origin [myRect release]; // Освобождение памяти для прямоугольника
Вы вынуждены помнить, что нужно освобождать память непосредственно для origin, хотя не вы выделяли эту память; это сделал класс Rectangle. В следующем разделе, «Замещающие методы», вы узнаете, как сделать, чтобы Rectangle освобождал память.
После перекомпиляции и перезапуска программы 8.5 с модифицированным методом появляются сообщения об ошибках (рис. 8.8).
Рис. 8.8. Сообщения компилятора об ошибках
Проблема возникает из-за того, что мы использовали в модифицированном методе некоторые методы из класса XYPoint, и теперь компилятору требуется больше информации об этом классе, чем дает директива @class. Нужно вернуться назад и заменить эту директиву импортом: #import "XYPoint.h
Вывод программы 8.5В Origin at (100, 200) Origin at (100, 200)
Это уже лучше. Теперь изменение значения myPoint на (50, 50) внутри main не окажет никакого влияния на координаты начала прямоугольника, поскольку копия этой точки была создана внутри метода setOrigin: объекта Rectangle. Мы не синтезировали здесь методы origin, поскольку
синтезированный метод-установщик setOrigin: будет действовать точно так же, как метод, написанный нами первоначально. По умолчанию синтезированный метод-установщик просто копирует указатель объекта, а не сам объект.Вы можете синтезировать другой тип метода-установщик, который создает копию объекта, но для этого вам нужно научиться писать копирующий метод. Мы вернемся к этой теме в главе 17. 8.3. Замещающие методы
Выше уже говорилось, что мы не можем удалить или обойти методы при наследовании, но можем изменить определение наследуемого метода путем замещения. Возвращаясь к двум классам, ClassA и ClassB, предположим, что нужно написать собственный метод initVar для ClassB. Мы уже знаем, что ClassB будет наследовать метод initVar, определенный в классе ClassA, но можно ли создать новый метод с тем же именем для замены наследуемого метода? Да, можно, для этого нужно просто определить новый метод с тем же именем. Метод, определенный с таким же именем, как в родительском классе, заменяет, или замещает (override), унаследованное определение. Новый метод должен иметь такой же тип возвращаемого значения и принимать такое же число аргументов такого же типа, как метод, который вы замещаете.
В программе 8.6 показан простой пример, отражающий эту концепцию. // Замещающие методы #import <Foundation/Foundation.h> // Объявление и определение класса ClassA @interface ClassA: NSObject { int x; -(void) initVar; @end @implementation ClassA -(void) initVar { x = 100; } @end // Объявление и определение класса ClassB @interface ClassB: ClassA -(void) initVar; -(void) printVar; @end @implementation ClassB -(void) initVar // добавляемый метод { x = 200; -(void) printVar { NSLog (@"x = %i", x); @end int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ClassB *b = [[ClassB alloc] init]; [b initVar]; // использование замещающего метода в В [b printVar]; // раскрытие значения х; [b release]; [pool drain]; return 0; }
Вывод программы 8.6 х = 200 Сообщение [b initVar];
вызывает использование метода initVar, определенного в ClassB, а не одноименного метода из ClassA, как в предыдущем примере (рис. 8.9).
Рис. 8.9. Замещение метода initVar Какой из методов выбирается?
Мы уже описывали, каким образом система выполняет поиск в иерархии, чтобы найти метод для применения к объекту. Если у вас есть методы с одинаковым именем в различных классах, то нужный метод выбирается в соответствии с классом получателя сообщения. В программе 8.7 используются такие же определения для классов ClassA и ClassB, как выше. #import <Foundation/Foundation.h> // Здесь нужно вставить определения для классов ClassA и ClassB int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ClassA *a = [[ClassA alloc] init]; ClassB *b = [[ClassB alloc] init]; [a initVar]; // использование метода из ClassA [a printVar]; // раскрытие значения x; [b initVar]; // использование замещающего метода из ClassB [b printVar]; // раскрытие значения x; [a release]; [b release]; [pool drain]; return 0; }
Для этой программы вы получите следующее предупреждающее сообщение: warning: 'ClassA' may not respond to ’-printVar’ (предупреждение: ’ClassA’, возможно, не отвечает '-printVar'
Что произошло? Рассмотрим объявление класса ClassA: // Объявление и определение класса ClassA @interface ClassA: NSObject { int x; -(void) initVar; @end
Обратите внимание, что не объявлен никакой метод printVar. Этот метод объявлен и определен в ClassB. И хотя объекты ClassB и их потомки могут использовать этот метод путем наследования, объекты класса ClassA не могут это сделать, поскольку данный метод определен ниже в цепочке иерархии.