Программирование на Objective-C 2.0
Шрифт:
Если объект принимает какой-либо формальный протокол, этот объект должен подчиняться всем требуемым сообщениям в этом протоколе. Это можно сделать как на этапе выполнения (runtime), так и во время компиляции. Если объект принимает неформальный протокол, то он не обязан принять все методы данного протокола (в зависимости от самого протокола). Подчинение неформальному протоколу можно сделать обязательным на этапе выполнения (с помощью respondsToSelector:), но не во время компиляции.
Примечание. Описанную выше директиву @optional (она была добавлена в Objective-C 2.0) можно использовать вместо неформальных протоколов. Она используется в нескольких классах UIKit (UIK.it — составная часть структур Cocoa Touch framework). 11.3.
Вы уже знаете несколько способов, позволяющих расширить определение класса с помощью таких средств, как подклассы и категории. Еще один способ — это определение класса, который содержит один или несколько объектов из других классов. Объект из этого класса называется составным (composite) объектом, поскольку он составлен из других объектов.
В качестве примера рассмотрим класс Square (квадрат), который мы определили в главе 8. Он был определен как подкласс класса Rectangle (прямоугольник), поскольку квадрат это прямоугольник с равными сторонами. Подкласс, который мы определяем наследует все переменные экземпляра и методы родительского класса. В некоторых случаях это нежелательно. Метод setWidth:andHeight: (задание ширины и высоты) класса Rectangle наследуется классом Square, но реально не относится к квадрату (хотя действует правильно). Кроме того, создавая подкласс, мы должны обеспечить правильность работы наследуемых методов, поскольку пользователи этого подкласса будут иметь к ним доступ.
Вместо подкласса можно определить новый класс, который содержит в качестве одной из своих переменных экземпляра объект из класса, который вы хотите расширить. Затем нужно определить в новом классе только те методы, которые для него подходят. В примере с классом Square можно определить Square следующим образом. @interface Square: NSObject { Rectangle *rect; } -(int) setSide: (int) s; -(int) side; (сторона) •(int) area; (площадь) -(int) perimeter; (периметр) @end
Здесь определен класс Square с четырьмя методами. В отличие от версии с подклассом, которая дает непосредственный доступ к методам класса Rectangle (setWidth:, setHeight:, setWidthiandHeight:, width и height), этих методов нет в определении для Square. Это имеет смысл, поскольку не все методы подходят для работы с квадратами.
Если мы определяем класс Square таким способом, то он становится ответственным за выделение памяти для прямоугольника (rectangle), который содержит. Например, без заметающих методов оператор Square *mySquare = [[Square alloc] init];
выделяет память для нового объекта типа Square, но не выделяет память для объекта типа Rectangle, хранящегося в его переменной экземпляра red.
Чтобы выполнить выделение памяти, требуется замещение метода init или добавление нового метода, например, initWithSide:. Этот метод может выделять память для переменной Rectangle red и задавать соответствующим образом сторону (side). Необходимо также заместить метод dealloc (как описано для класса Rectangle в главе 8), чтобы освободить память, используемую для Rectangle red, когда освобождается сам объект Square.
Определяя свои методы в классе Square, мы по-прежнему можем использовать методы класса Rectangle. Например, мы можем реализовать метод area следующим образом: -(int) area { return [rect area]; }
Реализацию остальных методов вы можете написать в качестве упражнения (см. ниже упражнение 5). Упражнения
Выполните расширение категории MathOps из программы 11.1, чтобы дополнительно включить метод invert, который возвращает дробь (Fraction), обратную получателю.
Добавьте в класс Fraction категорию с именем Comparison. В этой категории добавьте два метода в соответствии со следующими объявлениями.
– (BOOL) isEqualTo: (Fraction *) 1; -(int) compare: (Fraction *) f; Первый метод должен возвращать значение YES, если две дроби равны, и значение N0 в противном случае. Правильно сравнивайте дроби (например, при сравнении дробей 3/4 и 6/8 следует возвращать значение YES). Второй метод должен возвращать
Выполните расширение класса Fraction, добавив методы, которые подчиняются неформальному протоколу NSComparisonMethods, описанному з этой главе. Напишите реализацию первых шести методов из этого протокола (isEqualTo:, isLessThanOrEqualTo:, isLessThan:, isGreaterThanOrEquaHo:, isGreaterThan:,isNotEqualTo:) и выполните их тестирование.
Функции sin ,cos и tan {) включены в стандартную библиотеку Standard Library (как и scant ). Эти функции объявлены в header-файле , который вы должны импортировать в программу с помощью строки #import <math.h> Эти функции можно использовать для вычисления синуса, косинуса и тангенса аргумента типа double, выраженного в радианах. Возвращаемый результат является значением с плавающей точкой двойной точности. Например, для вычисления синуса аргумента d, где d — угол, выраженный в радианах, можно использовать следующую строку: result = sin (d); Добавьте категорию с именем Trig в класс Calculator, определенный в главе 6. Включите в эту категорию методы для вычисления синуса, косинуса и тангенса в соответствии со следующими объявлениями.
– (double) sin; -(double) cos; -(double) tan;
Напишите секцию implementation для Square и выполните тестирование программы для проверки ее методов, используя описание составных объектов из этой главы и следующую секцию interface, @interface Square: NSObject { Rectangle *rect; } -(Square*) initWithSide: (int) s; -(void) setSide: (int) s; -(int) side; -(int) area; -(int) perimeter; -(void) dealloc; // Замещающий метод для освобождения памяти объекта типа Rectangle @end
Глава 12. Препроцессор
Препроцессор содержит средства, упрощающие чтение, разработку и встраивание программ в различные системы. Препроцессор позволяет настроить язык Objective-C в соответствии с конкретным программным приложением или вашим стилем программирования.
Препроцессор — это составная часть процесса компиляции Objective-C. Он распознает специальные операторы и обрабатывает их, прежде чем будет выполнен анализ самой программы. Операторы препроцессора идентифицируются знаком «решетка» (#), который должен быть первым символом строки, отличным от пробела. Синтаксис операторов препроцессора несколько отличаюется от обычных операторов Objective-C. Мы начнем с описания оператора #define. 12.1. Оператор # define
Оператор #define позволяет присваивать символические имена программным константам. Оператор препроцессора #define TRUE 1
определяет имя TRUE и делает его эквивалентным значению 1. Затем имя TRUE можно использовать в любом месте программы, где могла бы использоваться константа 1. Там, где появляется это имя, препроцессор автоматически подставляет вместо него значение 1. Например, следующий оператор Objective-C использует определенное имя TRUE. gameOver = TRUE;
Этот оператор присваивает значение TRUE переменной gameOver. Вам не обязательно помнить, какое конкретное значение вы определили для TRUE. Приведенный выше оператор будет присваивать 1 переменной gameOver. Оператор препроцессора #define FALSE О
определяет имя FALSE и делает его эквивалентным значению 0. Например, оператор gameOver = FALSE;
присваивает значение FALSE переменной gameOver, а оператор if ( gameOver == FALSE)
сравнивает значение gameOver с определенным значением FALSE.
Определенное таким образом имя не является переменной, поэтому вы не можете присвоить ему значение, если результатом подстановки значения не янляется переменная. Если в программе используется определенное имя, препроцессор автоматически подставляет то, что представлено в правой части оператора «define. Это аналогично операции поиска и замены втекстозом редакторе; в данном случае препроцессор заменяет все экземпляры определенного имени соответствующим текстом.