Программирование на Objective-C 2.0
Шрифт:
Примечание. Система всегда содержит информацию о классе, которому принадлежит объект. Это позволяет ей принимать нужные решения во время выполнения, не во время компиляции. Подробнее об этом рассказывается в главе 13. Выбор методов из класса Fraction выполняется при оценке выражения с сообщением в зависимости от класса П и fracResult.
Возможность использования одного имени из различных классов называется полиморфизмом. Полиморфизм позволяет вам разрабатывать набор классов с одинаковым именем метода. В определение каждого класса включается код, необходимый для ответа на вызов данного определенного метода, и это делает его независимым от определений в других классах. Это позволяет также добавлять новые классы,
Примечание. Именно классы Fraction и Complex (а не тестовая программа) должны предусматривать освобождение памяти, занимаемой результатами их методов add:. На самом деле эти объекты должны освобождаться автоматически (autorelease). Подробнее об этом рассказывается в главе 18. 9.2. Динамическое связывание и тип id
В главе 4 уже говорилось, что тип данных id является обобщенным типом объекта. Это означает, что id используется для хранения объектов, которые принадлежат любому классу. Он используется для хранения в переменной различных типов объектов. Рассмотрим программу 9.2 и ее вывод. // Пример динамического контроля типов и динамического связывания #import Traction.h" #import "Complex.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id dataValue; Fraction *f1 = [[Fraction alloc] init]; Complex *c1 = [[Complex alloc] init]; [f1 setTo: 2 over: 5]; [c1 setReal: 10.0 andlmaginary: 2.5]; // в первый раз dataValue присваивается дробь (fraction) dataValue = f1; [dataValue print]; // теперь dataValue присваивается комплексное число (complex) dataValue = c1; [dataValue print]; [c1 release]; [f1 release]; [pool drain]; return 0; }
Вывод программы 9.2 2/5 10 + 2.5i
Переменная dataValue объявляется как объект типа id, поэтому dataValue можно использовать для хранения в программе объекта любого типа. Отметим, что в строке объявления не используется «звездочка»: id dataValue;
Объекту f 1 типа Fraction присваивается дробь 2/5, переменной с1 типа Complex присваивается значение (10 + 2.5i). Оператор dataValue = f 1;
сохраняет f1 в dataValue. Вы можете вызвать с dataValue любой из методов, допустимых в объекте типа Fraction, хотя dataValue имеет тип id, а не Fraction. Но как система определяет, какой метод нужно вызвать, если в dataValue можно сохранять объект любого типа? Если система встречает выражение с сообщением [dataValue print];
откуда она знает, какой метод print нужно вызвать? Ведь методы print определены и в классе Fraction, и в классе Complex.
Как уже говорилось, система Objective-C всегда следит за классом, которому принадлежит объект. Это также определяется концепциями динамического контроля типов и динамического связывания: система принимает решение о классе объекта и о методе для его динамического вызова во время выполнения, а не во время компиляции.
Поэтому во время выполнения программы, прежде чем передать dataValue сообщение print, система проверяет класс объекта, хранящегося в dataValue. В случае программы 9.2 эта переменная содержит объект типа Fraction, поэтому используется метод print, определенный в классе Fraction.
Во втором случае а с1 типа Complex присваивается dataValue. Затем выполняется следующее выражение с сообщением: [dataValue print];
Поскольку теперь dataValue содержит объект, принадлежащий классу Complex, для выполнения выбирается метод print из этого класса.
Это простой пример, но вы можете распространить эту концепцию на более сложные приложения. В сочетании с концепцией полиморфизма динамическое связывание и динамический контроль типов позволяют легко писать коды, которые передают одинаковое сообщение объектам из различных классов.
Например, метод draw можно использовать для рисования графических объектов на экране.
У вас могут быть различные методы draw, определенные для каждого из ваших графических объектов, таких как тексты, окружности, прямоугольники, окна и т.д. Если графический объект, который нужно нарисовать, сохраняется, например, в переменной типа id с именем currentObject, то его можно нарисовать на экране, просто передав ему сообщение draw: [currentObject draw];Вы можете сначала проверить, отвечает ли на метод draw объект, хранящийся в currentObject. Ниже вы увидите, как это делать. 9.3. Проверка на этапе компиляции и проверка на этапе выполнения
Поскольку тип объекта, хранящегося в переменной id, во время компиляции может быть неизвестен, некоторые проверки откладываются до выполнения программы (runtime). Рассмотрим следующую последовательность кодов. Fraction *f1 = [[Fraction alloc] init]; [f1 setReal: 10.0 andlmaginary: 2.5];
Поскольку метод setReakandlmaginary: применяется к комплексным числам, а не к дробям, при компиляции программы, содержащей эти строки, появится предупреждающее сообщение. ргодЗ.т: In function 'main':(B функции 'main') prog3.m:13: warning: ’Fraction' does not respond to 'setReakandlmaginary:' (предупреждение: объект типа 'Fraction' не отвечает на 'setReakandlmaginary:')
Компилятору Objective-C известно, что И является объектом класса Fraction, поскольку он был объявлен именно так. При появлении выражения с сообщением [Н setReal: 10.0 andlmaginary: 2.5];
ему стало известно, что класс Fraction не содержит метода setReal:andlmaginary: (и не наследует его). Поэтому компилятор выдает предупреждающее сообщение.
Теперь рассмотрим следующую последовательность кодов. id dataValue = [[Fraction alloc] init]; [dataValue setReal: 10.0 andlmaginary: 2.5];
Компилятор не выводит предупреждающего сообщения, поскольку во время обработки исходного файла ему неизвестно, какой тип объекта сохраняется в dataValue.
Сообщение об ошибке не появится, пока вы не запустите программу, содержащую эти строки. Сообщение об ошибке может выглядеть следующим образом. objc: Fraction: does not recognize selector -setReakandlmaginary: (не распознается селектор -setReakandlmaginary) dynamic3: received signal: Abort trap (получен сигнал: аварийное прерывание) When attempting to execute the expression (При попытке выполнить выражение) [dataValue setReal: 10.0 and I maginary: 2.5];
Система runtime сначала проверяет тип объекта, хранящегося внутри dataValue. Поскольку dataValue содержит дробь (объект Fraction), система runtime проверяет, определен ли метод setReal:andlmaginary: для класса Fraction. Поскольку это не так, выдается сообщение, и выполнение программы прекращается. 9.4. Тип данных id и статический контроль типов
Если тип данных id можно использовать для хранения любого объекта, так почему бы нам не объявлять все объекты с типом id? Этого не следует делать по нескольким причинам.
Во-первых, определяя переменную как объект из определенного класса, мы используем так называемый статический контроль типов (static typing). Слово статический означает, что переменная всегда используется для хранения объектов из определенного класса, поэтому класс объекта, хранящегося в этом типе, заранее определен, то есть является статическим. При использовании статического контроля типов компилятор обеспечивает согласованное использование этой переменной во всей программе. Компилятор может проверить, определен ли (или унаследован) метод, применяемый к объекту, и если нет, то выводит предупреждающее сообщение. Таким образом, если вы объявляете в своей программе переменную класса Rectangle с именем myRect, то компилятор проверяет, все ли методы, которые вы вызываете для myRect, определены в классе Rectangle или наследуются из его суперкласса.