Программирование на Objective-C 2.0
Шрифт:
Вывод программы 8.5 Origin at (Координаты начала) (100, 200) Origin at (50, 50)
В этой программе значение объекта myPoint было изменено с (100, 200) на (50, 50), то есть были изменены координаты начала прямоугольника. Но почему это произошло? Здесь не было явным образом задано новое значение начала прямоугольника, почему оно изменилось? Вернемся к определению метода setOrigin:, чтобы понять причину: -(void) setOrigin: (XYPoint *) pt { origin = pt; }
При вызове метода setOrigin: с помощью выражения myRect.origin = myPoint;
значение myPoint передается этому методу как аргумент. Это значение указывает место
Рис. 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, хотя не вы выделяли эту память; это сделал класс 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; }