Текст книги "Программирование на Objective-C 2.0"
Автор книги: Стивен Кочан
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 12 (всего у книги 32 страниц)
Вы уже познакомились с описателями хранения для класса, которые можно помещать перед именами переменных. Здесь мы рассмотрим другие описатели, которые предоставляют компилятору информацию о предполагаемом использовании переменной в программе. auto
Это ключевое слово используется для объявления автоматической локальной переменной (в противоположность статической). Это описатель по умолчанию для объявления переменной внутри функции или метода, но никто не использует его в явном виде. auto int index;
Это объявление переменной index как автоматической локальной переменной; это означает, что для нее выполняется автоматическое выделение памяти при входе в блок (которым может быть заключенная в фигурные скобки последовательность операторов, метод или функция) и автоматическое освобождение при выходе из этого блока. Поскольку это происходит по умолчанию внутри блока, оператор int index;
эквивалентен оператору auto int index;
В отличие от статических переменных, которые имеют по умолчанию начальные значения 0, автоматические переменные остаются неопределенными, пока вы не присвоите им значения в явном виде. const
Компилятор позволяет связывать атрибут const с переменными, значения которых не будут изменяться. Иначе говоря, он сообщает компилятору, что указанные переменные имеют постоянное значение при выполнении программы. Если попытаться присвоить значение переменной, объявленной с атрибутом const, после инициализации, или попытаться выполнить ее наращивание или уменьшение, компилятор выдаст предупреждающее сообщение. Ниже в переменной pi используется атрибут const: const double pi = 3.141592654;
Он указывает компилятору, что программа не будет изменять эту переменную. Естественно, значение такой переменной должно быть инициализировано при ее определении.
Определение переменной с атрибутом const указывает читателю, что программа не будет изменять значение этой переменной. volatile
Это описатель, противоположный const. Он в явном виде указывает компилятору, что соответствующая переменная будет изменять свое значение. Он включен в язык Objective-C, чтобы компилятор не оптимизировал операторы, которые кажутся избыточными, и выполнял повторную проверку переменной, когда ее значение, казалось бы, не изменяется. Типичный пример – порт ввода-вывода, (I/O) (подробнее см. главу 13).
Предположим, что у вас имеется адрес выходного порта, хранящегося в программе в переменной с именем outPort. Записать в этот порт два символа, например 0 и N, можно с помощью следующего кода. *outPort = 'O’; *outPort = ’N’;
В первой строке указывается, что символ 0 нужно сохранить по адресу памяти, указанному переменной outPort. Во второй строке указывается, что символ N нужно сохранить по тому же адресу. Оптимизирующий компилятор может заметить, что это две последовательные записи по одному адресу, и поскольку outPort между ними не изменяется, может просто удалить первый оператор из программы. Чтобы этого не произошло, нужно объявить переменную outPort с атрибутом volatile, например: volatile char *outPort; 10.4. Перечислимые типы данных
Язык Objective-C позволяет задавать диапазон значений, которые могут быть присвоены переменной. Определение перечислимого типа данных инициируется с помощью ключевого слова enum. Сразу после этого ключевого слова еле– дует имя перечислимого типа данных и затем список идентификаторов (заключенных в фигурные скобки), которые определяют допустимые значения для этого типа. В следующем операторе определяется тип данных flag: enum flag { false, true };
Теоретически этому типу данных внутри программы могут быть присвоены только значения true и false. К сожалению, компилятор Objective-C не выдает предупреждающего сообщения, если это правило нарушается.
Чтобы объявить переменную типа enum flag, нужно снова использовать ключевое слово enum, после которого следует имя перечислимого типа и список переменных. Например, в следующем операторе определяются две переменные типа flag: endOfData и matchFound. enum flag endOfData, matchFound;
Единственные значения, которые могут быть присвоены этим переменным – true и false. Следующие операторы являются допустимыми. endOfData = true; if ( matchFound == false )
Если вам нужно, чтобы определенное целое значение было связано с идентификатором перечисления, это целое значение можно присвоить идентификатору, когда определяется тип данных. Идентификаторам перечисления, которые появляются затем в списке, будут присвоены последовательные целые значения, начиная с указанного целого значения, увеличенного на I.
В следующем определении перечислимый тип данных direction определяется со значениями up, down, left и right. enum direction { up, down, left = 10, right}; (вверх, вниз, влево, вправо)
Компилятор присвоит значение 0 идентификатору up, поскольку он представлен первым в списке, присвоит 1 идентификатору down, поскольку он является следующим в списке, присвоит 10 идентификатору left, поскольку здесь явно задано это значение, и присвоит 11 идентификатору right, поскольку это увеличенное на 1 значение предыдущего идентификатора в списке.
Идентификаторы перечисления могут иметь одинаковое значение. Например, если в строке enum boolean { no = 0, false = 0, yes = 1, true = 1 };
булевой переменной типа enum присваивается значение по или false, то ей присваивается значение 0; если присваивается значение yes или true, то присваивается значение 1.
Ниже приводится еще один пример определения перечислимого типа данных. В нем определяется тип enum month с допустимыми значениями, которые могут быть присвоены переменной этого типа – названиями месяцев. enum month { january = 1, february, march, april, may, june, july, august, September, October, november, december};
Компилятор Objective-C интерпретирует идентификаторы перечисления как целые константы. Переменной thisMonth будет присвоено значение 2 (а не а не название месяца february), если программа содержит следующие две строки: enum month thisMonth; thisMonth = february;
В программе 10.3 используются перечислимые типы данных. В ней читается номер месяца, а затем выполняется оператор switch, чтобы определить, какой месяц был введен. Напомним, что перечислимые значения интерпретируются как целые константы, поэтому они являются допустимыми case-значениями.
Переменной days присваивается число дней в указанном месяце, и ее значение выводится после выхода из оператора switch. Для февраля (February) введена специальная проверка. // вывод числа дней месяца int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; enum month { january = 1, february, march, april, may, june, july, august, September, October, november, d ecem b er}; enum month amonth; int days; NSLog (@"Enter month number:"); scant ("%i", &amonth); switch (amonth) { case january: case march: case may: case july: case august: case October: case december: days = 31; break; case april: case june: case September: case november: days = 30; break; case february: days = 28; break; default: NSLog (@>"bad month number"); days = 0; break; } if ( days != 0) NSLog (@"Number of days is %i", days); if ( amonth == february) NSLog (@"...or 29 if it’s a leap year"); [pool drain]; return 0; }
Вывод программы 10.3 Enter month number: (Введите номер месяца) 5 Number of days is 31 (Число дней = 31) Вывод программы 10.3 (повторный запуск) Enter month number: 2 Number of days is 28 ...or 29 if it's a leap year (Число дней = 28 ... или 29, если это високосный год)
Вы можете явно присвоить целое значение переменной перечислимого типа данных; это делается с помощью оператора приведения типа (type cast). Н апример, если monthValue является переменной целого типа, имеющей значение 6, то следующее выражение является допустимым. lastMonth = (enum month) (monthValue • 1);
Если вы не используете оператор приведения типа, компилятор, к сожалению, нс реагирует на это.
При использовании программ с перечислимыми типами данных старайтесь не полагаться на то, что перечислимые значения интерпретируются как целые числа. Вместо этого их следует рассматривать как отдельные типы данных. Перечислимый тип данных позволяет связывать символическое имя с целым чис– лом. Если вам потребуется изменить значение этого числа, вам придется изменить его только в том месте, где определено перечисление. Если вы делаете предположения, основанные на фактическом значении перечислимого типа данных, то теряете преимущество перечисления.
При определении перечислимого типа данных имя типа данных может не указываться, и переменные могут быть объявлены как конкретный перечислимый тип данных, если этот тип определен.
Оператор enum { east, west, south, north } direction;
определяет неименованный перечислимый тип данных со значениями east, west, south и north (восток, запад, юг и север) и объявляет переменную (direction) этого типа.
Определение перечислимого типа данных в блоке ограничивает область действия этого определения данным блоком. С другой стороны, определение перечислимого типа данных в начале программы (вне какого-либо блока) делает определение глобальным для данного файла.
Определяя перечислимый тип данных, вы должны проследить, чтобы идентификаторы перечисления были уникальными по отношению к именам других переменных и идентификаторам перечисления, определенным в той же области действия. 10.5. Оператор typedef
Objective-C позволяет назначать для типа данных альтернативное имя. Для этого используется оператор typedef. В следующей строке определяется имя Counter (счетчик) как эквивалент типа данных Objective-C int. typedef int Counter;
Затем можно объявить переменные с типом Counter, как в следующей строке. Counter j, n;
Компилятор Objective-C будет интерпретировать это как объявление обычных целых переменных j и п. Определение j и п посредством typedef указывает назначение переменных в программе. Объявление в традиционной форме с типом int оставило бы их назначение непонятным.Ниже с помощью typedef определяется тип с именем NumberObject для объектов типа Number. typedef Number *NumberObject;
Переменные, которые объявляются затем с типом NumberObject, как в строке NumberObject myValuel, myValue2, myResult; будут интерпретироваться так, как если бы они были объявлены обычным образом: Number *myValue1, *myValue2, *myResult;
Чтобы определить имя нового типа с помощью typedef, нужно выполнить следующую процедуру.
Написать такое же объявление, как при объявлении переменной нужного типа.
Там, где должно быть имя объявляемой переменной, поместить имя нового типа.
Перед всем этим поставить слово typedef.
Для примера определим тип с именем Direction (Направление) как перечислимый тип данных со значениями east, west, north и south (восток, запад, юг и север), напишем определение этого перечислимого типа данных и подставим имя Direction там, где обычно ставится имя переменной. Перед всем этим нужно поместить ключевое слово typedef. typedef enum { east, west, south, north } Direction; После этого можно объявлять переменные с типом Direction: Direction stepl, step2; Foundation framework содерж ит следую щ ее определение typedef для NSComparisonResult в одном из заголовочных файлов. enum NSComparisonResult { NSOrderedAscending = -1, NSOrderedSame, NSOrderedDescending }; typedef NSInteger NSComparisonResult;
Некоторые методы в Foundation framework, которые выполняют сравнение (comparison), возвращают значение этого типа. Например, метод сравнения строк Foundation с именем compare: возвращает значение типа NSComparisonResult после сравнения двух строк, которые являются объектами NSString. Этот метод объявляется следующим образом. -(NSComparisonResult) compare: (NSString *) string;
Чтобы проверить равенство двух объектов NSString с именами userName и savedName, можно включить в программу следующую строку. if ( [userName compare: savedName] == NSOrderedSame) { // Имена совпадают }
На самом деле здесь проверяется, равен ли нулю результат метода compare: 10.6. Преобразования типов данных
В главе 4 говорилось о том, что иногда при оценке выражений система неявным образом выполняет преобразования. Там рассматривался случай с типами данных float и int. Операция, включающая типы float и int, выполнялась как операция с плавающей точкой, и элемент данных целого типа автоматически преобразовывался в элемент с плавающей точкой.
Там же было показано, как использовать оператор приведения типа, чтобы явно определить преобразование. Пусть total и п являются целыми переменными. В строке average = (float) total / n;
значение переменной total преобразуется в тип float до выполнения операции, что гарантирует выполнение деления как операции с плавающей точкой. Правила преобразования
Компилятор Objective-C соблюдает строгие правила при оценке выражений, состоящих из разл ичных типов данных.
Ниже описывается порядок, в котором выполняются преобразования при оценке двух операндов в выражении.
Если один из операндов имеет тип long double, второй операнд преобразуется в long double, результат имеет такой же тип.
Если один из операндов имеет тип double, второй операнд преобразуется в double, результат имеет такой же тип.
Если один из операндов имеет тип float, второй операнд преобразуется в тип float, результат имеет такой же тип.
Если один из операндов имеет тип Bool, char, short int или bit field1 или перечислимый тип данных, то он преобразуется в тип int.
Если один из операндов имеет тип long long int, второй операнд преобразуется в long long int, результат имеет такой же тип.
Если один из операндов имеет тип long int, второй операнд преобразуется в long int, результат имеет такой же тип.
Если мы дошли до этого шага, то оба операнда имеют тип int, результат имеет такой же тип.
Это упрощенная версия шагов преобразования операндов в выражении. Правила усложняются, если включены операнды без знака (unsigned). Полный список правил см. в Приложении В.
Из этой последовательности шагов понятно, что если достигнут шаг, где говорится «результат имеет такой же тип», процесс преобразования закончен.
В качестве примера рассмотрим, в каком порядке выполняется оценка следующего выражения. В нем f имеет тип float, i – тип int, I – long int, s – переменная типа short int. f * i + I / s
1 В главе 13 кратко описывается тип bit field.
Рассмотрим сначала умножение f на i, то есть умножение переменной типа float на переменную типа int. Из шага 3 известно, что поскольку f имеет тип float, второй операнд (i) будет тоже преобразован в тип float, и такой же тип будет иметь результат операции умножения.
Затем I делится на s, то есть выполняется деление переменной типа long int на short int. Из шага 4 известно, что short int преобразуется в int. Переходим к шагу 6. Поскольку один из операндов (1) имеет тип long int, второй операнд преобразуется в long int, и результат будет иметь такой же тип. Таким образом, это деление дает значение типа long int, причем дробная часть отбрасывается.
И, наконец, шаг 3 указывает, что поскольку один из операндов выражения имеет тип float (как результат умножения f * i), второй операнд будет преобразован в тип float, и результат будет иметь такой же тип. Таким образом, после деления I на s результат операции будет преобразован в тип float и затем прибавлен к произведению f на i. Поэтому конечный результат этого выражения будет иметь тип float.
Вы всегда можете использовать оператор приведения типа для явных преобразований, управляя оценкой конкретного выражения.
Например, если вы не хотите, чтобы при делении I на s в предыдущем выражении отбрасывалась дробная часть, то можете выполнить приведение одного из этих операндов к типу float, чтобы выполнить деление с плавающей точкой: f * i + (float) I / s
В этом выражении I будет преобразована в тип float до операции деления, поскольку оператор приведения типа имеет более высокий приоритет, чем оператор деления. Поскольку один из операндов деления будет теперь иметь тип float, другие операнды будут автоматически преобразованы в тип float, и такой же тип будет иметь результат. Расширение для знака
Если переменная типа signed int или signed short преобразуется в тип int или тип большего размера, то в результате преобразования слева тоже появляется описатель signed. Поэтому переменная типа short int, которая имеет значение -5, будет иметь значение -5 после преобразования в long int. Но если целая переменная с описателем unsigned преобразуется в тип int или тип большего размера, то расширение для знака не происходит (как и можно было ожидать).
На некоторых машинах (например, с процессорами Intel, которые используются в современных семействах компьютеров Macintosh, или с процессорами ARM, которые используются в iPhone и iTouch) символы интерпретируются как значения со знаком. Это означает, что если символ (character) преобразуется в целый тип, то происходит расширение для знака. Если используются символы из стандартного набора символов ASCII, это не создает проблем. Но если значение символа не является частью этого стандартного набора символов, для его знака может быть выполнено расширение, если символ преобразуется в целый тип. Например, на компьютерах Мае символьная константа '377’ преобразуется в значение -1, потому что это значение является отрицательным, если рассматривать его как 8-битное значение со знаком (signed).
Objective-C позволяет объявлять символьные переменны е без знака (unsigned), что позволяет избежать этой потенциальной проблемы – переменная unsigned char не получает расширения для знака при преобразовании в целый тип; ее значение всегда больше или равно нулю. Для обычного 8-битного символа символьная переменная со знаком имеет диапазон значений от -128 до +127 включительно. Символьная переменная без знака имеет диапазон значений от О до 255 включительно.
Если ваши символьные переменные должны получать расширение для знака, вы можете объявить такие переменные с типом signed char. В главе 15 вы узнаете о многобайтных символах Unicode. Это предпочтительный способ работы со строками. Упражнения
Используя класс Rectangle из главы 8, добавьте метод-инициализатор в соответствии со следующим объявлением. -(Rectangle *) initWithWidth: (int) w: andHeight: (int) h;
С учетом того, что мы назвали метод, разработанный в упражнении 1, назначенным (designated) инициализатором для класса Rectangle, и основываясь на определениях классов Square и Rectangle из главы 8, добавьте методинициализатор в класс Square в соответствии со следующим объявлением. -(Square *) initWithSide: (int) side;
Добавьте счетчик (counter) к методу add: класса Fraction, чтобы вычислять количество вызовов этого метода. Каким образом вы можете считывать значение этого счетчика?
Используя typedef и перечислимые типы данных, определите тип с именем Day (День) с возможными значениями Sunday, Monday, Tuesday, Wednesday, Thursday, Friday и Saturday (Воскресенье, Понедельник, Вторник, Среда, Четверг, Пятница и Суббота).
Используя typedef, определите тип с именем FractionObj, который позволяет писать следующие операторы. FractionObj И = [[Fraction alloc] init], f2 = [[Fraction alloc] init];
Используя определения float f = 1.00; short int i = 100; long int I = 500L; double d = 15.00; и семь шагов, описанных выше для преобразования операндов в выражениях, определите тип и значение следующих выражений. f + i l/d i / l + f 1 * i f / 2 i / (d + f) 1 / (i * 2.0) I + i / (double) I
Напишите программу, которая определяет, выполняется ли на вашей машине расширение для знака у переменных signed char.
Глава 11. Категории и протоколы
В этой главе описывается, как добавлять методы для класса в модульном стиле с помощью категорий и как создавать стандартизованный список методов для реализации другими людьми. 11.1. Категории
При работе с определенным классом к нему приходится добавлять новые методы. Например, для класса Fraction могут потребоваться методы, реализующие вычитание, умножение и деление двух дробей.
Предположим, что ваша группа в составе большого проекта определяет новый класс, который содержит много различных методов. Вашей задачей является написание для этого класса методов, которые работают с файловой системой. Другие члены проекта должны написать методы, которые реализуют создание и инициализацию экземпляров этого класса, выполняют операции над объектами в этом классе и рисуют представления объектов этого класса на экране.
Вы изучили, как использовать класс массивов из библиотеки Foundation framework с именем NSArray и поняли, что в этом классе необходимо реализовать один или нескольких методов. Конечно, вы могли бы написать новый подкласс класса NSArray и реализовать эти новые методы, но есть более простой способ. На практике для разрешения подобных ситуаций используются категории (category). Категория представляет простой способ модульного определения класса в виде групп или категорий связанных методов. Она также позволяет достаточно просто расширить существующее определение класса без доступа к существующему исходному коду этого класса или создания подкласса. Это мощная и достаточно простая для изучения концепция.
Вернемся к первому примеру и покажем, как добавить новую категорию в класс Fraction для работы с четырьмя основными арифметическими операциями. Приведем исходную секцию interface для Fraction. #import
Теперь удалим метод add: из этой секции interface и добавим его в новую категорию вместе с тремя другими арифметическими операциями, которые нужно реализовать. Ниже показана секция in te rfa c e д л я новой категории MathOpS. #import «Fraction.h» @interface Fraction (MathOps) -(Fraction *) add: (Fraction *) f; -(Fraction *) mul: (Fraction *) f; -(Fraction *) sub: (Fraction *) f; -(Fraction *) div: (Fraction *) f; @end
Здесь представлено некоторое определение секции interface, по на самом деле это расширение существующей секции. Мы должны включить исходную секцию interface, чтобы указать компилятору на класс Fraction (правда, вы можете включить эту новую категорию непосредственно в исходный файл Fraction.h).
После строки «import мы вилим следующую строку: @interface Fraction (MathOps)
Она указывает компилятору, что для класса Fraction определяется новая категория с именем MathOps. Имя категории после имени класса заключено в круглые скобки. Отметим, что здесь не указывается родительский класс для Fraction; компилятору уже извес тно о нем из Fraction.h. Кроме того, мы ничего не сообщаем ему о переменных экземпляра, хотя делали это во всех предыдущих секциях interface. На самом деле мы получим от компилятора сообщение о синтаксической ошибке, если попытаемся включить родительский класс или переменные экземпляра.
Эта секция interface указы вает компилятору, что мы добавляем в класс Fraction расширение под категорией с именем MathOps. Категория MathOps содержит четыре метода экземпляра: add:, mul:, sub: и div:. Каждый метод получает в качестве аргумента дробь (fraction) и возвращает тоже дробь.
Определения всех этих четырех методов можно поместить в одну секцию implementation.Мы можем определить в одной секции implementation все методы из секции interface файла Fraction.h плюс методы из категории MathOps. Мы также можем определить методы из этой категории в отдельной секции implementation. Тогда в секции implementation для этих методов следует также идентифицировать категорию, к которой принадлежат эти методы. Как и в секции interface, имя категории после имени класса нужно заключить: в круглые скобки @implementation Fraction (MathOps) // код для методов этой категории ... @end
В программе 11.1 для новой категории MathOps секции interface и implementation объединены в одном файле вместе с тестовой процедурой. #import "Fraction.h" @interface Fraction (MathOps) -(Fraction *) add: (Fraction *) f; -(Fraction *) mul: (Fraction *) f; -(Fraction *) sub: (Fraction *) f; -(Fraction *) div: (Fraction *) f; @end @implementation Fraction (MathOps) -(Fraction *) add: (Fraction *) f { // Для сложения двух дробей: // a/b + c/d = ((a*d) + (b*c)) / (b * d) Fraction *result = [[Fraction alloc] init]; int resultNum, resultDenom; resultNum = (numerator * f.denominator) + (denominator * f.numerator); resultDenom = denominator * f.denominator; [result setTo: resultNum over: resultDenom]; [result reduce]; return result; } -(Fraction *) sub: (Fraction *) f // Для вычитания двух дробей: // a/b – c/d = ((a*d) – (b*c)) / (b * d) Fraction *result = [[Fraction alloc] init]; int resultNum, resultDenom; resuUNum = (numerator * f.denominator) – (denominator * {.numerator); resultDenom = denominator * f.denominator; [result setTo: resultNum over: resultDenom]; [result reduce]; return result; } -(Fraction *) mul: (Fraction *) f { Fraction *result = [[Fraction alloc] init]; [result setTo: numerator * f.numerator over: denominator * f.denominator]; [result reduce]; return result; } -(Fraction *) div: (Fraction *) f { Fraction *result = [[Fraction alloc] init]; [result setTo: numerator * f.denominator over: denominator * {.numerator]; [result reduce]; return result; } @end int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *a = [[Fraction alloc] init]; Fraction *b = [[Fraction alloc] init]; Fraction *result; [a setTo: 1 over: 3]; [b setTo: 2 over: 5]; [a print]; NSLog (@" +"); [b print]; NSLog (@"–"); result = [a add: b]; [result print]; NSLog (@ "n"); [result release]; [а print]; NSLog (@" -"); [b print]; NSLog (@"–"); result = [a sub: b]; [result print]; NSLog (@"n"); [result release]; [a print]; NSLog (@" *"); [b print]; NSLog (@"–"); result = [a mul: b]; [result print]; NSLog (@"n"); [result release]; [a print]; NSLog (@" /"); [b print]; NSLog (@"–"); result = [a div: b]; [result print]; NSLog (@"n"); [result release]; [a release]; [b release]; [pool drain]; return 0; }
Вывод программы 11.1 1/3 + 2/5 – 11/15 1/3 – 2/5 – -1/15 1/3 * 2/5 – 2/15 1/3 / 2/5 – 5/6
И снова напомним, что в Objective-C вполне допустимы такие операторы, как [[a div: b] print];
В этой строке выполняется непосредственный вывод результата деления а на Ь, что позволяет избежать промежуточного присваивания результата какой– либо переменной, как в программе 11.1.Нам нужно это промежуточное присваивание, чтобы получить результирующую дробь (Fraction) и освободить память, которую она занимает. В противном случае при каждом выполнении арифметической операции над дробью будет происходить утечка памяти.
В программе 11.1 секции interface и implementation для новой категории помещены в один файл вместе с тестовой программой. Как уже говорилось выше, секция interface для категории может быть включена в исходный header-файл Fraction.h (чтобы все методы были объявлены в одном месте), или в свой соб-ственный header-файл.
Если поместить категорию в мастер-файл определения класса, все пользователи этого класса будут иметь доступ к методам данной категории. Если у вас пет возможности непосредственного внесения изменений в исходный header– файл (см. добавление категории в существующий класс из библиотеки в части II, «Foundation Framework»), то вы вынуждены хранить категорию в отдельном файле. Некоторые замечания по категориям
Отметим несколько особенностей категорий. Во-первых, хотя категория имеет доступ к переменным экземпляра исходного класса, в ней нельзя добавить ее собственные переменные экземпляра. При необходимости нужно использовать подклассы.
Кроме того, ка тегория может замещать другой метод своего класса. Обычно это считается недопустимым в практике надежного программирования. После замещения метода вы уже нс имеете доступа к исходному методу, поэтому при замене вы должны аккуратно дублировать все функциональные возможности замещаемого метода. Если вам действительно требуется замещение какого-либо метода, используйте подклассы. При замещении метода в подклассе вы можете по-прежнему обращаться к родительскому методу, передавая сообщение super. В этом случае вам не нужно знать все особенности метода, который вы замешаете; можно просто вызвать родительский метод и добавить ваши функциональ-ные возможности в метод подкласса.
Соблюдая изложенные здесь правила, можно иметь сколько угодно категорий. Если метод определен более чем в одной категории, язык не указывает, какая из них будет использоваться.
В отличие от обычной секции interface, вам не нужно реализовать все методы, указанные в категории. Это полезно для пошаговой разработки программ, поскольку вы можете объявить все методы в категории и постепенно их реализовывать.
Расширение класса путем добавления новых методов с помощью категории влияет не только на этот класс, по и на все его подклассы. Это может быть потенциально опасным. Например, если вы добавляете новые методы в корневой объект NSObject, каждый пользователь будет наследовать эти методы. Новые методы, которые вы добавляете к существующему классу с помощью категории, возможно, будут отвечать вашим намерениям, но могут оказаться несогласованными с исходной организацией или целями класса. Например, превращение квадрата (Square) в окружность (Circle) путем добавления новой категории и некоторых методов искажает определение класса и не согласуется с практикой надежного программирования.
Кроме того, пары объект/категория должны быть уникальными. Только одна категория NSString (Private) может существовать в заданном пространстве имен Objective-C. Это может вызывать затруднения, поскольку пространство имен Objective-C совместно используется кодом программы и всеми библиотеками, структурами framework (фреймворками) и дополнительными программными модулями (plug-in). Это особенно важно для программистов Objective-C, которые пишут коды экранных заставок (screensaver), панелей предпочтений и других дополнительных модулей, поскольку их код будет вставляться в код приложения или фреймворк, которыми они не могут управлять. 11.2. Протоколы
Протокол (protocol) – это список методов, которые совместно используются классами. Методы, включенные в протокол, не имеют соответствующих реализаций (implementation): предполагается, что они будут реализованы кем-то другим. Протокол – это способ определения набора методов, которые каким-либо образом связаны с указанным именем. Эти методы обычно документируются, что позволяет вам реализовать их в ваших определениях классов.