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

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

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


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



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

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

Вернемся к примеру и добавим метод printVar в класс ClassA, чтобы вывести значение его переменных экземпляра. // Объявление и определение класса ClassA @interface ClassA: NSObject { int x; -(void) initVar; -(void) printVar; @end @implementation ClassA -(void) initVar { x = 100; } -(void) printVar { NSLog (@"x = %i", x); } @end

Объявление и определение класса ClassB остается без изменений. Запустим компиляцию и выполнение программы.

Вывод программы 8.7 х= 100 х = 200

а и b определены как объекты классов ClassA и ClassB соответственно. После выделения памяти и инициализации передается сообщение для объекта а, у которого запрашивается применение метода initVar. Этот метод определен в определении класса ClassA, поэтому выбирается именно он. Он присваивает значение 100 переменной экземпляра х и выполняет возврат. Затем вызывается метод printVar, только что добавленный в класс ClassA, чтобы вывести значение х. Выделение памяти и инициализация для объекта b класса ClassB выполняется так же как и для объекта класса ClassA, его переменной экземпляра присваивается значение 200 и выводится ее значение.

Постарайтесь разобраться, как для переменных а и b происходит выбор метода, исходя из класса, которому они принадлежат. Это одна из базовых концепций объектно-ориентированного программирования в Objective-C.

В качестве упражнения попробуйте удалить метод printVar из класса ClassB. Получится ли это? Почему? Замещение метода dealloc и ключевое слово super

Теперь, когда вы знаете, как замещать методы, вернемся к программе 8.5В, чтобы изучить более подходящий способ освобождения памяти, выделенной для origin. Метод setOrigin: выделяет память для своего собственного объекта origin класса XYPoint, и вы обязаны освободить эту память. В программе 8.6 освобождение памяти выполнял оператор: [[myRect origin] release];

Вам не нужно заботиться об освобождении всех отдельных членов класса; вы можете заместить метод dealloc (наследуемый из NSObject) и освободить там память origin.

Примечание. Мы будем замещать метод dealloc, а не метод release. Метод release иногда освобождает память, используемую объектом, а иногда нет. Он освобождает память, используемую объектом, только если никто другой не обращается к этому объекту, и делает это, вызывая метод объекта dealloc, который фактически освобождает память.

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

Для этого существует специальное ключевое слово super, которое обозначает родительский класс получателя сообщения. Для выполнения замещаемого метода нужно передать super сообщение. Выражение с сообщением [super release];

при использовании внутри метода вызывает метод release, который определен (или унаследован) в родительском классе. Этот метод вызывается в получателе сообщения, то есть в себе самом (self).

Таким образом, замещение метода dealloc для класса Rectangle выполняется следующим образом. Сначала освобождается память, занятая origin, а затем вызывается метод dealloc из родительского класса. Тем самым освобождается память, занятая самим объектом Rectangle. Ниже приводится этот метод. -(void) dealloc { if (origin) [origin release]; [super dealloc]; }

Определенный здесь метод dealloc не возвращает никакого значения. Внутри метода сначала dealloc выполняется проверка, что origin имеет ненулевое значение. Начало прямоугольника (origin), возможно, не было задано. В этом случае он имеет по умолчанию нулевое значение. Затем вызывается метод dealloc из родительского класса, который был бы унаследован классом Rectangle, если бы не был замещен.

Метод dealloc можно написать проще: -(void) dealloc { [origin release]; [super dealloc]; }

поскольку вы можете без проблем передать сообщение nil-объекту. Кроме того, к origin применяется release, а не dealloc. В любом случае, если никто другой не использует origin, release вызовет метод dealloc для origin, чтобы освободить его пространство. Используя этот метод, вы должны освобождать только те объекты-прямоугольники, для которых выделили память, не заботясь об объектах XYPoint, которые они содержат. Двух сообщений, показанных в программе 8.5, теперь достаточно, чтобы освободить память для всех объектов, в том числе объекта XYPoint, создаваемого с помощью setOrigin:. [myRect release]; [myPoint release];

Правда, остается одна проблема. Если задать для начала прямоугольника (origin) одного объекта Rectangle другие значения во время выполнения программы, то вы должны освободить память, занятую прежним началом прямоугольника, прежде чем выделить и назначить новую. Рассмотрим следующую последовательность строк: myRect.origin = startPoint; myRect.origin = endPoint; [startPoint release]; [endPoint release]; [myRect release];

Копия объекта startPoint класса XYPoint, сохраненная в элементе origin myRect, не будет освобождена, поскольку она перезаписывается вторым значением для origin (endPoint). Эта копия origin будет освобождена правильно, когда будет освобождаться сам объект прямоугольника, если применяется новый метод освобождения памяти.

Но вы должны сделать так, чтобы память, выделенная для предыдущего начала (origin), была освобождена до того, как будет задано новое начало. Это можно сделать в методе setOrigin:. -(void) setOrigin: (XYPoint *) pt { if (origin) [origin release]; origin = [[XYPoint alloc] init]; [origin setX: pt.x andY: pt.y]; }

Если вы синтезируете свои методы доступа, то можете сделать так, чтобы компилятор автоматически разрешил эту проблему. 8.4. Расширение через наследование: добавление новых переменных экземпляра

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

Вернемся к простым классам ClassA и ClassB и внесем некоторые изменения. Добавим в ClassB новую переменную экземпляра у. @interface ClassB: ClassA { int у; } -(void) printVar; @end

Может показаться, что ClassB будет иметь только одну переменную экземпляра (с именем у), но, исходя из предыдущего объявления, на самом деле он имеет две. Он имеет его собственную переменную экземпляра у и наследует переменную х из класса ClassA.

Примечание. Этот класс имеет также переменные экземпляра, которые наследует из класса NSObject, но мы пока будет игнорировать этот факт. Ниже приводится простой пример этой концепции (программа 8.8). // Расширение переменных экземпляра #import // Объявление и определение ClassA @interface ClassA: NSObject { int x; } -(void) initVar; @end @implementation ClassA -(void) initVar { x = 100; } @end // Объявление и определение ClassB @interface ClassB: ClassA { int у; } -(void) initVar; -(void) printVar; @end @implementation ClassB -(void) initVar x = 200; у = 300; ) -(void) printVar { NSLog (@"x = %i", x); NSLog (@"y = %i", y); } @end int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ClassB *b = [[ClassB alloc] init]; [b initVar]; // uses overriding method in ClassB [b printVar]; // reveal values of x and y; [b release]; [pool drain]; return 0; }

Вывод программы 8.8 x = 200 у = 300

Объект b класса ClassB инициализируется путем вызова метода initVar, определенного в классе ClassB. Этот метод замещает метод initVar из ClassA, присваивает значение 200 переменной х (унаследованное из ClassA) и значение 300 – переменной у (определенной в ClassB). Метод printVar выводит значения этих переменных экземпляра.

Есть много других тонкостей, связанных с выбором метода в ответ на сообщение, особенно в тех случаях, когда получателем является один из нескольких классов. Это динамическое связывание (dynamic binding), которое рассматривается в следующей главе. 8.5. Абстрактные классы

Мы закончим эту главу, добавив еще немного терминологии, связанной с понятием наследования.

Иногда классы создаются просто для того, чтобы упростить другим создание подкласса. Такие классы называют абстрактными (abstract) или абстрактными суперклассами. В этом классе определяются методы и переменные экземпляра, но при этом не предполагается, что кто-либо будет создавать экземпляры из этого класса. Например, р нет никакого смысла определить объект непосредственно из корневого объекта NSObject. Foundation framework (см. часть II) содержит несколько абстрактных классов. Например, класс Foundation NSNumber является абстрактным классом, который был создан для работы с числами как с объектами. Для хранения целых чисел и чисел с плавающей точкой обычно требуются разные размеры памяти. Для каждого числового типа существуют отдельные подклассы NSNumber. Поскольку эти подклассы, в отличие от их абстрактных суперклассов, реально существуют, их называют конкретными (concrete) подклассами. Каждый конкретный подкласс является дочерним классом класса NSNumber и называется кластером (cluster). Когда вы отправляете сообщение классу NSNumber для создания нового объекта типа integer, используется соответствующий подкласс, чтобы выделить память для этого объекта и задать его значение. Эти подклассы на самом деле являются частными. Вы не можете выполнить непосредственный доступ к этим подклассам сами; доступ осуществляется косвенно через абстрактный суперкласс. Абстрактный суперкласс представляет общий интерфейс для работы со всеми типами числовых объектов и позволяет вам не знать, какой тип числа вы сохранили в вашем числовом объекте и как задавать и считывать его значение. Упражнения

Добавьте в программе 8.1 новый класс с именем ClassC, который является подклассом класса ClassB. Создайте метод initVar, который присваивает значение 300 своей переменной экземпляра х. Напишите тестовую процедуру, которая объявляет объекты классов ClassA, ClassB и ClassC и вызывает их методы initVar.

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

Внесите изменения в программу 8.1, чтобы добавить новый класс с именем ClassB2, который, как и ClassB, является подклассом класса ClassA. Что вы можете сказать о связи между ClassB и ClassB2? Укажите иерархические связи между классом NSObject, классом ClassA, ClassB и ClassB2. Что является суперклассом для ClassB? Что является суперклассом для ClassB2? Сколько подклассов может иметь класс, и сколько он может иметь суперклассов?

Напишите метод Rectangle с именем translate:, который принимает в качестве своего аргумента вектор с именем XYPoint (xv,yv). Сделайте так, чтобы он смещал начало прямоугольника (origin) на указанный вектор.

Определите новый класс с именем GraphicObject и сделайте его подклассом NSObject. Определите переменные экземпляра в этом новом классе следующим образом. int fillColor; // 32-битный цвет BOOL filled; // Заполняется ли объект? int lineColor; // 32-битный цвет линии

Напишите методы, которые задают и считывают значения определенных выше переменных.

Сделайте класс Rectangle подклассом GraphicObject. Определите новые классы Circle (Круг) и Triangle (Треугольник), которые тоже являются подклассами GraphicObject. Напишите методы, чтобы задавать и считывать параметры для этих объектов, вычислять длину окружности и площадь круга, периметр и площадь треугольника.

Напишите метод для Rectangle с именем intersect:, который принимает прямоугольник как аргумент и возвращает прямоугольник, представляющий площадь перекрытия этих прямоугольников. Например, для двух прямоугольников, показанных на рис. 8.Ю, метод должен возвращать прямоугольник, начало (origin) которого находится в точке (400, 420), ширина (width) равна 50 и высота (height) равна 60.

Рис. 8.10. Пересечение прямоугольников

Если прямоугольники не пересекаются, нужно возвратить прямоугольник с шириной и высотой 0 и началом (0,0).

Напишите метод для класса Rectangle с именем draw, который рисует прямоугольник из знаков «минус» и «вертикальная черта». Следующая последовательность кодов Rectangle *myRect = [[Rectangle alloc] init]; [myRect setWidth: 10 and Height: 3]; [myRect draw]; [myRect release]; _______ | | | | | | _______

Глава 9. Полиморфизм, динамический контроль типов и динамическое связывание

В этой главе описываются возможности языка Objective-C, которые делают его столь мощным языком программирования и отличают его от некоторых объектно-ориентированных языков, таких как C++. Это три ключевых концепции: полиморфизм, динамический контроль типов и динамическое связывание.

Полиморфизм (polymorphism) позволяет разрабатывать программы таким образом, чтобы объекты из различных классов могли определять методы, которые имеют одно и то же имя. Динамический контроль типов (dynamic typing) позволяет откладывать определение класса, которому принадлежит объект, до выполнения программы. Динамическое связывание (dynamic binding) позволяет откладывать определение конкретного метода для вызова в объекте до начала выполнения программы. 9.1. Полиморфизм: одно имя, различные классы

В программе 9.1 показан файл секции interface для класса Complex, который используется для представления в программе комплексных чисел. Программа 9.1. Файл секции interface Complex.h // файл секции interface для класса Complex #import @interface Complex: NSObject { double real; double imaginary; } @property double real, imaginary; -(void) print; -(void) setReal: (double) a andlmaginary: (double) b; -(Complex *) add: (Complex *) f; @end

В упражнении 6 главы 4 мы создали секцию implementation для этого класса. Мы добавили дополнительный метод setReal:andlmaginary:, чтобы задавать вещественную и мнимую части числа с помощью одного сообщения, а также синтезировали методы доступа. Это показано в следующей программе. Программа 9.1. Файл секции implementation Complex.m // Файл секции implementation для класса Complex #import "Complex.h" @implementation Complex @synthesize real, imaginary; -(void) print { NSLog (@" %g + %gi", real, imaginary); } -(void) setReal: (double) a andlmaginary: (double) b { real = a; imaginary = b; } -(Complex *) add: (Complex *) f { Complex *result = [[Complex alloc] init]; [result setReal: real + [f real] andlmaginary: imaginary + [f imaginary]]; return result; } @end

Программа 9.1. Тестовая программа main.m // Совместно используемые имена методов: полиморфизм #import "Fraction.h #import "Complex.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *f1 = [[Fraction alloc] init]; Fraction *f2 = [[Fraction alloc] init]; Fraction *fracResult; Complex *c1 = [[Complex alloc] init]; Complex *c2 = [[Complex alloc] init]; Complex *compResult; [f1 setTo: 1 over: 10]; [f2 setTo: 2 over: 15]; [c1 setReal: 18.0 andlmaginary: 2.5]; [c2 setReal: -5.0 andlmaginary: 3.2]; // добавление и вывод 2 комплексных чисел [d print]; NSLog (@" +"); [с2 print]; NSLog (@и............ "); compResult = [d add: с2]; [compResult print]; NSLog (@"n"J; [c1 release]; [c2 release]; [compResult release]; // добавление и вывод 2 дробей [f1 print]; NSLog (@" +"); [f2 print]; NSLog (<§>"–"); fracResult = [f1 add: f2]; [fracResult print]; [f1 release]; [f2 release]; [fracResult release]; [pool drain]; return 0; }

Вывод программы 9.1 18 + 2.5i + -5 + 3.2i 13 + 5.7i 1/10 + 2/15 7/30

Отметим, что оба класса, Fraction и Complex, содержат методы add: и print. Но откуда система знает, какие методы нужно вызывать при выполнении следующих выражений с сообщениями? compResult = [d add: с2]; [compResult print];

Системе выполнения (runtime) Objective-C известно, что с1, получатель первого сообщения, является объектом класса Complex. Поэтому выбирается метод add:, определенный для класса Complex. Система выполнения Objective-C определяет также, что compResult является объектом класса Complex, поэтому она выбирает метод print, определенный в классе Complex, чтобы вывести результат сложения. То же самое относится к следующим выражениям с сообщениями. fracResult = [f1 add: f2]; [fracResult print];

Примечание. Система всегда содержит информацию о классе, которому принадлежит объект. Это позволяет ей принимать нужные решения во время выполнения, не во время компиляции. Подробнее об этом рассказывается в главе 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 или наследуются из его суперкласса.

Примечание. Определенные приемы позволяют вызывать методы, который указываются самой переменной, в таком случае компилятор не может выполнить проверку.

Но если проверка будет выполнена во время выполнения, почему вас должен интересовать статический контроль типов? Дело в том, что лучше выявить ошибки на этапе компиляции программы, чем на этапе выполнения. Если программу будет выполнять другой человек, вы не сможете увидеть ошибку. Это означает, что если программа введена в эксплуатацию, ничего не подозревающий пользователь может столкнуться с ошибкой, при которой указывается, что определенный объект не распознает некоторый метод.

Еще одним доводом к применению статического контроля типов является то, что он делает ваши программы более удобными для чтения. Рассмотрим объявление id f1;

и сравним его с Fraction *f1;

Конечно, второе объявление лучше, поскольку в нем указывается предполагаемое использование переменной И. Сочетание статического контроля типов и осмысленных имен переменных позволяет делать программы самодоку– ментируемыми. Типы аргументов и возвращаемых значений при динамическом контроле типов

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

Компилятор выполняет проверку на согласованность в объявлениях каждого класса, которые он встречает. Если один или несколько методов не согласуются с типом аргумента или возвращаемого значения, компилятор выводит предупреждающее сообщение. Например, оба класса, Fraction и Complex, содержат метод add:, но класс Fraction принимает в качестве аргумента и возвращает объект типа Fraction, а класс Complex – объект типа Complex. Если trad и myFract – объекты типа Fraction, a compl и myComplex – объекты типа Complex, то определения result = [myFract add: trad];

и result = [myComplex add: compl];

не вызовут проблемы. В обоих случаях получатель сообщения доступен для статического контроля типов и компилятор может проверить согласованность при использовании метода, поскольку он определен в классе получателя. Если dataValuel и dataValue2 – переменные типа id, то выражение result = [dataValuel add: dataValue2];

заставляет компилятор генерировать код для передачи данного аргумента методу add: и обработки его возвращаемого значения, делая некоторые предположения.

Во время выполнения система runtime Objective-C проверит конкретный класс объекта, хранящегося в dataValuel, и выберет метод из подходящего класса для выполнения. Однако в более общем случае компилятор может генерировать неверный код для передачи методу аргументов или обработки его возвращаемого значения. Это может произойти, например, если один метод принял в качестве аргумента какой-либо объект, а другой – значение с плавающей точкой, или один метод возвратил объект, а другой – целое значение. Если методы отличаются только типом объекта (например, метод add: класса Fraction принимает в качестве аргумента и возвращает объект типа Fraction, а метод add: класса Complex принимает в качестве аргумента и возвращает объект типа Complex), то компилятор же будет генерировать правильный код, поскольку адреса памяти (то есть указатели) в обоих случаях будут передаваться как ссылки на объекты. 9.5. Как задавать вопросы о классах

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

Является ли данный объект прямоугольником (rectangle)?

Поддерживает ли данный объект метод print?

Является ли данный объект членом класса Graphics или одного из его потомков?

Ответы на такие вопросы можно использовать, чтобы выполнять разные последовательности кода, чтобы избежать возникновения ошибок или проверить целостность программы во время ее выполнения.

В таблице 9.1 приводится сводка основных методов, поддерживаемых классом Object. В таблице объект-класс (object-class) – это объект, получаемый для заданного класса (обычно он генерируется с помощью метода class), а с е л е к т о р (s e le c to r ) – это значение типа SEL (обычно создается с помощью директивы @selector).

Табл. 9.1. Методы для работы с динамическими типами Метод Вопрос или действие -(BOOL) isKindOfClass: объект-класс Является ли объект членом о б ъ е к т а -к л а с с а или дочернего класса? -(BOOL) isMemberOfClass: объект-класс Является ли объект членом о б ъ е к т а – к л а с с а ? -(BOOL) respondsToSelector: селектор +(B00L) instancesRespondToSelector: селектор +(B00L)isSubclass0fClass: объект-класс Может ли объект отвечать на метод, указанный с е л е к т о р о м ? Могут ли экземпляры указанного класса отвечать на с е л е к т о р ? Является ли объект подклассом указанного класса? -(id) performSelector: selector -(id) performSelector: селектор withObject: объект -(id) performSelector: селектор withObject: объект 1 withObject: объект2 Применение метода, указанного селектором. Применение метода, указанного селектором , с передачей аргумента о б ъ е к т . Применение метода, указанного селектором , с передачей аргументов объект1 и объект2.

Имеются и другие методы. Один из них позволяет спрашивать, согласуется ли объект с определенным протоколом (см. главу 11). Другие позволяют спрашивать о динамически разрешаемых методах (в этой книге они не рассматриваются).

Чтобы создать объект-класс из имени класса или другого объекта, нужно передать ему сообщение class. Например, чтобы получить объект-класс из класса с именем Square, напишите строку [Square class]

Если mySquare является экземпляром объекта типа Square, то для получения его объекта-класса нужно написать [mySquare class]

Чтобы проверить, являются ли объекты, хранящиеся в переменных obj1 и obj2, экземплярами одного класса, нужно написать строку if ([obj1 class] == [obj2 class])

Чтобы убедиться, что объект myFract является членом класса Fraction, нужно проверить результат выражения [myFract isMemberOfClass: [Fraction class]]


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

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