355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Стивен Кочан » Программирование на Objective-C 2.0 » Текст книги (страница 9)
Программирование на Objective-C 2.0
  • Текст добавлен: 19 сентября 2016, 13:02

Текст книги "Программирование на Objective-C 2.0"


Автор книги: Стивен Кочан



сообщить о нарушении

Текущая страница: 9 (всего у книги 32 страниц)

Результаты вывода показывают, что происходит при запуске программы. При значении 5 сумма последовательности равна 0.96875. При 15 результат очень близок к 1. Расширение определений класса и файл секции interface

К настоящему моменту мы разработали небольшую библиотеку методов для работы с дробями. Ниже приводится файл секции interface, в котором применяется все, что мы умеем делать с использованием этого класса. #import // Определение класса Fraction @interface Fraction : NSObject { int numerator; int denominator; } @property int numerator, denominator; -(void) print; -(double) convertToNum; -(Fraction *) add: (Fraction *) f; -(void) reduce; @end

Мы постепенно уточняли и расширяли класс путем добавления новых методов. Этот файл секции interface можно передать другому человеку, чтобы он мог писать программы, в которых операции с дробями. Если ему потребуется добавить новый метод, он сможет сделать это непосредственно, расширив определение класса, или косвенно, определив собственный подкласс и добавив свои собственные новые методы. Этому посвящена следующая глава. Упражнения

Добавьте следующие методы к классу Fraction для завершения списка арифметических операций над дробями. Выполните сокращение (reduce) результата в каждом случае. // Вычитание аргумента из получателя -(Fraction *) subtract: (Fraction *) f; // Умножение получателя на аргумент -(Fraction *) multiply: (Fraction *) f; // Деление получателя на аргумент -(Fraction *) divide: (Fraction *) f;

Модифицируйте метод print из класса Fraction, чтобы он мог принимать необязательный аргумент типа B00L, который указывает, нужно ли выполнить сокращение дроби для ее вывода. Если дробь должна быть сокращена, сделайте так, чтобы сама дробь не изменялась необратимо.

Внесите изменения в программу 7.6, чтобы выводить результирующую сумму (sum) как дробь, а не только как вещественное число.

Будет ли ваш класс Fraction работать с отрицательными дробями? Например, можно ли сложить -1/4 и -1/2 и получить правильный результат? Напишите тестовую программу, чтобы проверить свой ответ.

Внесите изменения в метод print класса Fraction, чтобы выводить дроби, превышающие 1, как смешанные числа (с целой и дробной частями), например, дробь 5/3 выводить как 1 2/3.

В упражнении 6 главы 4 определяется новый класс с именем Complex для работы с комплексными мнимыми числами. Добавьте новый метод с именем add:, который можно использовать для сложения двух комплексных чисел. Чтобы сложить два комплексных числа, нужно по отдельности сложить вещественные и мнимые части: (5.3 + 7i) + (2.7 + 4i) = 8 + 11i Этот метод add: должен сохранять и возвращать результат в виде нового числа типа Complex в соответствии с объявлением метода: -(Complex *) add: (Complex *) complexNum; Учтите в тестовой программе все потенциальные проблемы утечки памяти.

Для класса Complex, разработанного в упражнении 6 главы 4, с учетом расширения, сделанного в упражнении 6 этой главы, создайте отдельные файлы секций interface и implementation —Complex.h и Complex.m. Создайте отдельный файл тестовой программы.

Глава 8. Наследование

В этой главе рассматривается один из ключевых принципов, который делает объектно-ориентированное программирование столь мощным. Принцип наследования позволяет расширять существующие определения классов и настраивать их для приложений. 8.1. Все начинается с корня

Мы уже рассматривали идею родительского класса в главе 3. Родительский класс может быть дочерним классом другого родительского класса. Класс, не имеющий никакого родительского класса, находится вверху иерархии и называется корневым классом (root class). В Objective-C вы можете определять собственный родительский класс, но обычно используют возможности существующих классов. Все классы, которые мы определяли до настоящего момента, являются дочерними классами (потомками) корневого класса с именем NSObject, который указывался в файле секции interface следующим образом: @interface Fraction: NSObject @end

Класс Fraction образуется из класса NSObject. Поскольку NSObject находится вверху иерархической структуры (то есть над ним уже нет никаких классов), он называется корневым классом (рис. 8.1). Класс Fraction является дочерним (child) классом или подклассом (subclass).

Рис. 8 .1. Корневой класс и подкласс

С точки зрения терминологии, можно говорить о классах, родительских классах и дочерних классах или о классах, подклассах и суперклассах. Если определяется какой-либо новый класс (не корневой), он наследует определенные свойства. Например, все переменные экземпляра и методы из родительского класса неявным образом становятся частью определения нового класса. Это означает, что соответствующий подкласс имеет непосредственный доступ к методам и переменным экземпляра, как будто они были определены непосредственно в определении этого подкласса.

Проиллюстрируем концепцию наследования с помощью простого (хотя и несколько надуманного) примера. Рассмотрим объявление объекта ClassA, содержащего один метод с именем initVar. @interface ClassA: NSObject { int x; } -(void) initVar; (@end

Метод initVar просто присваивает значение 100 переменной экземпляра класса ClassA. @implementation ClassA -(void) initVar { x = 100; } @end

Теперь определим класс с именем ClassB. @interface ClassB: ClassA -(void) printVar; @end

В первой строке этого объявления @interface ClassB: ClassA

указывается, что ClassB объяслвается не как подкласс NSObject, а как подкласс класса ClassA. И хотя родительским классом (суперклассом) для класса ClassA является NSObject, родительским классом для ClassB является ClassA (рис. 8.2). На этом рисунке корневой класс не имеет суперкласса, a ClassB, который находится внизу иерархии, не имеет подкласса. Тем самым ClassA является подклассом NSObject, ClassB является подклассом класса ClassA, а также класса NSObject (он является его «подподклассом», или внуком). Кроме того, NSObject является суперклассом для ClassA, который является суперклассом для ClassB. NSObject является также суперклассом для ClassB.

Ниже приводится полное определение для ClassB, где определяется один метод с именем printVar. @interface ClassB: ClassA -(void) printVar; @end (^implementation ClassB -(void) printVar { NSLog (@"x = %i", x); } @end

Метод printVar выводит значение переменной экземпляра х, и при этом мы не определяем никаких переменных экземпляра в ClassB. Поскольку ClassB является подклассом для ClassA, он наследует все переменные экземпляра из ClassA (в данном случае – только одну переменную). Это показано на рис. 8.3.

Рис. 8.2. Подклассы и суперклассы

Рис. 8.3. Наследование переменных экземпляра и методов

(Конечно, на рис. 8.3 не показаны другие методы или переменные экземпляра, наследуемые из класса NSObject.)

Теперь рассмотрим, как это все сочетается в программе. Для краткости поместим все объявления и определения в один файл. // Простой пример наследования #import // Объявление и определение ClassA @interface ClassA: NSObject { int x; } -(void) initVar; @end @implementation ClassA -(void) initVar { x = 100; } @end // Объявление и определение ClassB @interface ClassB : ClassA -(void) printVar; @end @implementation ClassB -(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.1 х = 100

Мы начинаем с определения b как объекта класса ClassB. После выделения памяти и инициализации b следует передача сообщения для применения к b метода initVar. Н о в определении класса ClassB мы не видим, что определен какой-либо метод. Метод initVar был определен в классе ClassA, а поскольку ClassA является родительским классом для ClassB, он может использовать все методы ClassA. Метод initVar является унаследованным методом по отношению к ClassB. Примечание. Методы alloc и init, которые мы все время используем, никогда не определялись, поскольку мы применяем их как унаследованные методы.

После отправки объекту b сообщения initVar вызывается метод printVar для вывода значения переменной экземпляра х. Результат вывода х = 100 подтверждает, что printVar смог получить доступ к этой переменной экземпляра, поскольку он тоже был унаследован.

Концепция наследования действует вниз по всей цепочке. Так, если определить новый класс с именем ClassC, родительским классом для которого является ClassB: @interface ClassC: ClassB; @end

то ClassC унаследует все методы и переменные экземпляра класса ClassB, который, в свою очередь, наследует методы и переменные экземпляра класса ClassA, который, в свою очередь, наследует методы и переменные экземпляра NSObject.

Каждый экземпляр класса получает свои собственные переменные экземпляра, даже если они наследуются. Это означает, что объект класса ClassC и объект класса ClassB будут иметь свои собственные отдельные переменные экземпляра. Поиск подходящего метода

Прежде чем передавать сообщение объекту, надо выбрать подходящий метод для применения к этому объекту. Здесь действуют совершенно простые правила. Сначала нужно проверить класс, которому принадлежит данный объект. Если метод с нужным именем определен в этом классе, применяется этот метод. Если это не так, проверяется родительский класс, и если в нем определен данный метод, то применяется этот метод. В противном случае поиск продолжается.

Проверка родительских классов выполняется до тех пор, пока не найдется класс, который содержит указанный метод, или метод не будет найден даже в корневом классе. В первом случае вы можете действовать дальше; во втором случае возникает проблема и генерируется сообщение, аналогичное следующему: warning: 'ClassB' may not respond to '-inity* (предупреждение: 'ClassB', возможно, не отвечает ’-inity’)

Это означает, что вы ошибочно пытаетесь передать сообщение с именем unity переменной типа ClassB. Компилятор указывает, что переменные этого типа «не знают», как реагировать на такой метод. Это было определено после проверки методов класса ClassB и методов его родительских классов вплоть до корневого класса (которым в данном случае является NSObject).

В некоторых случаях, когда метод не найден, никакое сообщение не генерируется. Это означает, что используется пересылка (forwarding), описание которой приводится в главе 9. 8.2. Расширение посредством наследования: добавление новых методов

Наследование часто используется для расширения класса. Предположим, требуется разработать несколько классов для работы с двумерными графическими объектами, такими как прямоугольник, окружность и треугольник. В данном случае нас интересуют только прямоугольники (rectangle). Вернемся к упражнению 7 главы 4 и начнем с секции @interface. @interface Rectangle: NSObject { int width; int height; } @property int width, height; -(int) area; -(int) perimeter; @end

У вас будут синтезируемые методы для задания ширины (width, w) и высоты (height, h) прямоугольника, а также возврата этих значений, и ваши собственные методы для вычисления его площади (area) и периметра (perimeter). Добавим метод, который позволит задавать ширину и высоту прямоугольника в одном сообщении: -(void) setWidth: (int) w andHeight: (int) h;

Предположим, что это объявление нового класса введено в файл с именем Rectangle.h. Файл секции implementation с именем Rectangle.m может выглядеть следующим образом. #import "Rectangle.h" @implementation Rectangle @synthesize width, height; -(void) setWidth: (int) w andHeight: (int) h width = w; height = h; -(int) area { return width * height; -(int) perimeter { return (width + height) * 2; } @end

Каждое определение метода достаточно очевидно. В программе 8.2 показана процедура main для тестирования. #import "Rectangle.h" #import int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; [myRect setWidth: 5 andHeight: 8]; NSLog (@"Rectangle: w = %i, h = %i", myRect.width, myRect.height); NSLog (@"Area = %i, Perimeter = %i", [myRect area], [myRect perimeter]); [myRect release]; [pool drain]; return 0; }

Вывод программы 8.2 Rectangle (прямоугольник): w = 5, h = 8 Area (площадь) = 40, Perimeter (периметр) = 26

Сначала выполняются выделение памяти и инициализация объекта myRect; затем задается его ширина (5) и высота (8). Это проверяется в первой строке вывода. Затем вычисляются площадь и периметр прямоугольника путем вызова с помощью сообщения, и возвращаемые значения передаются для вывода процедуре NSLog.

Для работы с квадратами (square) можно было бы определить новый класс с именем Square и определить в нем методы, аналогичные методам класса Rectangle. Можно также учесть, что квадрат является частным случаем прямоугольника, у которого равны ширина и высота.

Поэтому проще создать новый класс с именем Square и сделать его подклассом класса Rectangle. Это позволит использовать все методы и переменные класса Rectangle помимо ваших собственных. После этого достаточно добавить только методы задания определенного значения для стороны квадрата и считывания этого значения. В программе 8.3 показаны файлы секций interface и implementation для нового класса Square. #import "Rectangle.h" @interface Square: Rectangle -(void) setSide: (int) s; -(int) side; @end #import "Square.h" @implementation Square: Rectangle -(void) setSide: (int) s { [self setWidth: s andHeight: s]; } (int) side { return width; } @end

Мы определили класс Square как подкласс класса Rectangle, который объявлен в заголовке файла Rectangle.h. Здесь не требуется добавлять какие-либо переменные экземпляра, но добавлены новые методы setSide: и side.

Для квадрата достаточно задать только одну сторону, которая представляется как два числа. Все это скрыто от пользователя класса Square. Пользователю не нужно думать об этих деталях, поскольку здесь действует инкапсуляция, о которой мы говорили раньше.

В методе setSide: используется метод, наследуемый из класса Rectangle, задающий ширину и высоту прямоугольника. Таким образом, setSide: вызывает метод setWidth:andHeight: из класса Rectangle, передавая параметр s как значение для ширины (width) и высоты (height).Больше ничего не требуется. Тот, кто будет работать с объектом класса Square, может задавать размеры квадрата с помощью метода setSide: и использовать методы из класса Rectangle для вычисления площади и периметра квадрата. Ниже показана тестовая программа 8.3 и ее вывод для нашего нового класса Square. #import "Square.h" #import int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Square *mySquare = [[Square alloc] init]; [mySquare setSide: 5]; NSLog (@"Square s = %i", [mySquare side]); NSLog (@"Area = %i, Perimeter = %i", [mySquare area], [mySquare perimeter]); [mySquare release]; [pool drain]; return 0; }

Вывод программы 8.3 Square (квадрат) s = 5 Area (площадь) = 25, Perimeter (периметр) = 20

Способ, с помощью которого был определен класс Square, является базовым методом работы с классами в Objective-C: расширение того, что уже кем-то сделано, чтобы реализовать то, что вам нужно. Помимо этого, существует механизм категорий (category), который позволяет добавлять новые методы к существующему определению класса по модульному принципу, то есть без необходимости постоянно добавлять новые определения в файлы секций interface и implementation. Это особенно удобно в тех случаях, когда вы не имеете доступа к исходному коду. Описание категорий см. в главе 11. Класс для точки и выделение памяти

Класс Rectangle используется только для хранения размеров прямоугольника. В реальных графических приложениях требуется следить за всевозможной дополнительной информацией, например, цветом заполнения прямоугольника, цветом линий, точкой начала координат прямоугольника (origin) внутри окна и т.д. Для этого можно легко расширить существующий класс. Сейчас мы реализуем идею начала координат прямоугольника. Примем за начало координат декартовы координаты (х, у) левого нижнего угла прямоугольника. Если вы разрабатываете чертежное приложение, эта точка может представлять местоположение прямоугольника внутри окна (рис. 8.4).

Рис. 8.4. Прямоугольник, нарисованный в окне На рис. 8.4 точка начала координат прямоугольника представлена как (xl,y 1). Вы можете расширить класс Rectangle, чтобы сохранять координаты х,у точки начала прямоугольника в виде двух отдельных значений или определить класс с именем XYPoint (возможно, вы помните об этой задаче из упражнения 7 главы 3). #import @interface XYPoint: NSObject { int x; int y; } @property int x, y; (void) setX: (int) xVal andY: (int) yVal; @end

Теперь вернемся к классу Rectangle. Нам нужно сохранять координаты начала прямоугольника, поэтому требуется добавить к определению этого класса еще одну переменную экземпляра с именем origin. @interface Rectangle: NSObject { int width; int height; XYPoint *origin; }

Вполне разумно добавить метод, с помощью которого задаются и считываются координаты начала прямоугольника. Мы не будем здесь синтезировать методы доступа (accessor methods) для координат начала, а напишем их сами. Директива @class

На данный момент вы можете работать с прямоугольниками (и квадратами), задавая их ширину, высоту и координаты начала. Рассмотрим в полном виде файл Rectangle.h секции interface. #import @class XYPoint; @interface Rectangle: NSObject { int width; int height; XYPoint *origin; @property int width, height; -(XYPoint *) origin; -(void) setOrigin: (XYPoint *) pt; -(void) setWidth: (int) w andHeight: (int) h -(int) area; -(int) perimeter; @end

В файле Rectangle.h использована новая директива: @class XYPoint;

Она требуется нам, поскольку компилятору нужно знать, что представляет собой XYPoint, когда он встречается в одной из переменных экземпляра, определенных для Rectangle. Имя этого класса используется также в объявлениях типов аргумента и возвращаемого значения для наших методов setOrigin: и origin соответственно. У вас есть и другой вариант выбора – импортировать файл заголовка, например, в следующем виде: #import "XYPoint.h"

Директива @class эффективнее, поскольку компилятору не нужно обрабатывать весь файл XYPoint.h (хотя это небольшой файл); компилятору достаточно знать, что XYPoint является именем класса. Если нужна ссылка на один из методов класса XYPoint, то директива @class недостаточна, поскольку компилятору потребуется дополнительная информация: аргументы, передаваемые методу, их типы, тип возвращаемого значения метода.

Заполним формы для нового класса XYPoint и новых методов класса Rectangle, чтобы протестировать их в программе. В программе 8.4 имеется файл секции implementation для класса XYPoint.

Сначала в ней показаны новые методы для класса Rectangle. #import "XYPoint.h" -(void) setOrigin: (XYPoint *) pt { origin = pt; -(XYPoint *) origin { return origin; ) @end

Затем показаны полные определения классов XYPoint и Rectangle и тестовая программа для их проверки. #import @interface XYPoint: NSObject { int x; int y; } @property int х, у; -(void) setX: (int) xVal andY: (int) yVal; @end #import "XYPoint.h" @implementation XYPoint @synthesize x, y; -(void) setX: (int) xVal andY: (int) yVal { x = xVal; у = yVal; } @end #import @class XYPoint; @interface Rectangle: NSObject { int width; int height; XYPoint *origin; } @property int width, height; -(XYPoint *) origin; -(void) setOrigin: (XYPoint *) pt; -(void) setWidth: (int) w andHeight: (int) h; -(int) area; -(int) perimeter; @end #import "Rectangle.h" @implementation Rectangle @synthesize width, height; -(void) setWidth: (int) w andHeight: (int) h width = w; height = h; -(void) setOrigin: (XYPoint *) pt origin = pt; -(int) area { return width * height; } -(int) perimeter { return (width + height) * 2; } -(Point *) origin { return origin; } @end #import "Rectangle.h" #import "XYPoint.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; XYPoint * myPoint = [[XYPoint alloc] init]; [myPoint setX: 100 andY: 200]; [myRect setWidth: 5 andHeight: 8]; myRect.origin = myPoint; NSLog (@"Rectangle w = %i, h = %i", myRect.width, myRect.height); NSLog (@"Origin at (%i, %i)"> myRect.origin.x, myRect.origin.y); NSLog (@»Area = %i, Perimeter = %i», [myRect area], [myRect perimeter]); [myRect release]; [myPoint release]; [pool drain]; return 0; }

Вывод программы 8.4 Rectangle (Прямоугольник) w = 5, h = 8 Origin at (Координаты начала) (100, 200) Area (Площадь) = 40, Perimeter (Периметр) = 26

Внутри процедуры main выделена память и инициализирован объект класса Rectangle с именем myRect и объект класса XYPoint с именем myPoint. С помощью метода setX:andY: объекту myPoint присваивается значение (100, 200). После задания ширины и высоты этого прямоугольника (5 и 8 соответственно) вызывается метод setOrigin, чтобы задать для координат начала прямоугольника точку, указанную в myPoint. Затем с помощью трех вызовов процедуры NSLog выполняется считывание и вывод этих значений. В выражении myRect.origin.x

считывается объект класса XYPoint, возвращенный методом доступа origin, и применяется оператор «точка» для получения координаты «х» начала прямоугольника. В следующем выражении считывается координата «у» начала прямоугольника: myRect.origin.y Классы, владеющие своими объектами

Можете ли вы объяснить результаты вывода программы 8.5? #import "Rectangle.h" #import "XYPoint.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; XYPoint *myPoint = [[XYPoint alloc] init]; [myPoint setX: 100 andY: 200]; [myRect setWidth: 5 and Height: 8]; myRect.origin = myPoint; NSLog (@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y); [myPoint setX: 50 andY: 50]; NSLog (@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y); [myRect release]; [myPoint release]; [pool drain]; return 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 передается этому методу как аргумент. Это значение указывает место в памяти, где хранится данный объект 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 // Объявление и определение класса 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 // Здесь нужно вставить определения для классов 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 не могут это сделать, поскольку данный метод определен ниже в цепочке иерархии.

Примечание. Вы могли бы принудительно использовать этот метод, но мы не будем описывать способы обхода, поскольку это не соответствует практике надежного программирования.


    Ваша оценка произведения:

Популярные книги за неделю