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

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

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


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



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

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

Чтобы генерировать один из так называемых селекторов, которые приводятся в таблице 9.1, нужно применить директиву @selector к имени метода. В следующей строке создается значение типа SEL для метода с именем alloc, который является наследуемым методом из класса NSObject. (@selector (alloc)

В следующем выражении создается селектор для метода setTo:over:, который мы реализовали в классе Fraction (вспомните о двоеточияхв именах методов). (@selector (setTo:over:)

Чтобы убедиться, что экземпляр класса Fraction отвечает на метод setTo:over:, можно проверить возвращаемое значение из следующего выражения. [Fraction instancesRespondToSelector: (@selector (setTo:over:)]

Эта проверка охватывает и наследуемые, и непосредственно определенные в определении класса методы.

Метод performSelector: и его вариации (не показанные в таблице 9.1) позволяют передавать объекту сообщение, где сообщение может быть селектором, хранящимся внутри переменной. Рассмотрим следующую последовательность кода. SEL action; id graphicObject; action = @selector (draw); [graphicObject performSelector: action];

Метод, указанный переменной action типа SEL, передается графическому объекту, который хранится в graphicObject. Предполагается, что действие (action) может изменяться во время выполнения программы в зависимости от ввода пользователя, несмотря на указанное действие draw. Чтобы убедиться, что объект может ответить на данное действие, нужно использовать, например, следующую последовательность. if ([graphicObject respondsToSelector: action] == YES) [graphicObject perform: action] else // здесь должен быть код для обработки ошибок

Примечание. Вы можете отлавливать ошибку, переопределив метод doesNotRecognize:. Этот метод вызывается, когда классу передано нераспознаваемое сообщение и в качестве аргумента передан нераспознаваемый селектор. Можно применять и другие стратегии. Например, с помощью метода forward:: можно перенаправлять сообщение для обработки. Всегда можно передавать метод и перехватывать возникшую исключительную ситуацию. Мы рассмотрим этот способ чуть позже.

В программе 9.3 задаются некоторые вопросы о классах Square и Rectangle, определенных в главе 8. Постарайтесь предсказать результаты этой программы. #import "Square.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Square *mySquare = [[Square alloc] init]; // Проверка с помощью isMemberOf: if ( [mySquare isMemberOfClass: [Square class]] == YES ) NSLog (@"mySquare is a member of Square class"); if ( [mySquare isMemberOfClass: [Rectangle class]] == YES ) NSLog (@"mySquare is a member of Rectangle class"); if ( [mySquare isMemberOfClass: [NSObject class]] == YES ) NSLog (@"mySquare is a member of NSObject class"); II Проверка с помощью isKindOf: if ( [mySquare isKindOfClass: [Square class]] == YES ) NSLog (@"mySquare is a kind of Square"); if ( [mySquare isKindOfClass: [Rectangle class]] == YES ) NSLog (@"mySquare is a kind of Rectangle"); if ( [mySquare isKindOfClass: [NSObject class]] == YES ) NSLog (@"mySquare is a kind of NSObject"); // Проверка с помощью respondsTo: if ( [mySquare respondsToSelector: @selector (setSide:)] == YES ) NSLog (@"mySquare responds to setSide: method"); if ( [mySquare respondsToSelector: @selector (setWidth:andHeight:)] == YES ) NSLog (@"mySquare responds to setWidth:andHeight: method"); if ( [Square respondsToSelector: @selector (alloc)] == YES ) NSLog (@"Square class responds to alloc method"); // Проверка с помощью instancesRespondTo: if ([Rectangle instancesRespondToSelector: @selector (setSide:)] == YES) NSLog (@"lnstances of Rectangle respond to setSide: method"); if ([Square instancesRespondToSelector: @selector (setSide:)] == YES) NSLog (@"lnstances of Square respond to setSide: method"); if ([Square isSubclassOfClass: [Rectangle class]] == YES) NSLog (@"Square is a subclass of a rectangle"); [mySquare release]; [pool drain]; return 0; }

Эта программа должна быть создана с файлами секции implementation для классов Square, Rectangle и XYPoint, которые были рассмотрены в главе 8.

Вывод программы 9.3 mySquare is a member of Square class (mySquare является членом класса Square) mySquare is a kind of Square (mySquare происходит из Square) mySquare is a kind of Rectangle mySquare is a kind of NSObject mySquare responds to setSide: method (mySquare отвечает на метод setSide:) mySquare responds to setWidthiandHeight: method Square class responds to alloc method (Square отвечает на метод alios) Instances of Square respond to setSide: method (Экземпляры mySquare отвечают на метод setSide:) Square is a subclass of a rectangle (Square – это подкласс класса Rectangle)

Вывод программы 9.3 вполне понятен. isMemberOfClass: прозеряет непосредственное членство в классе, a isKindOfClass: проверяет членство в иерархии наследования. mySquare является членом класса Square и «происходит» из Square, Rectangle и NSObject, поскольку входи т в иерархию этою класса (очевидно, что все объекты должны возвращать значение YES для проверки isKindOf: по классу NSObject, если только вы не определили новый корневой объект).

В проверке if ( [Square respondsTo: @selector (alloc)] == YES )

определяется, отвечает ли класс Square на метод класса alloc. Это так, поскольку он унаследован из корневого объекта NSObject. Вы всегда можете использовать непосредственно имя класса в качестве получателя в выражении для сообщения и не обязаны включать [Square class]

в предыдущее выражение (хотя могли бы это сделать).

Но это единственное место, где можно без этого обойтись. В других местах для получения объекта-класса нужно обязательно применять метод class. 9.6. Обработка исключительных ситуаций с помощью @try

Практика надежного программирования обязывает предусматривать проблемы, которые могут возникнуть в программе. Это можно делать, проверяя состояния, которые могут вызвать аварийное завершение программы, и включая обработку этих ситуаций, например, выводя сообщение и корректно завершая программу. Например, выше в этой главе было показано, как проверить, отвечает ли объект на определенное сообщение. Эта проверка позволяет избежать отправки нераспознаваемого сообщения. Обычно при попытке отправки нераспознаваемою сообщения профамма сразу прекращает свою работу, выдавая гак называемую исключительную ситуацию, или исключение (exception).

Рассмотрим программу 9.4. В определении класса Fraction у нас не было метода с именем noSuchMethod («нет такого метода»). При компиляции этой программы вы получите из-за этого предупреждающие сообщения. #import Traction.h" int main (int arge, char *argv []) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *f = [[Fraction alloc] init]; [f noSuchMethod]; NSLog (@"Execution continues!'’); [f release]; [pool drain]; return 0; }

Несмотря на эти предупреждающие сообщения, вы можете попытаться продолжить работу и выполнить программу. В этом случае программа будет аварийно завершена с выводом следующих сообщений об ошибках. Вывод программы 9.4 -[Fraction noSuchMethod]: unrecognized selector sent to instance 0x103280 (... нераспознанный селектор передан экземпляру) *** Terminating арр due to uncaught exception ’NSInvalidArgumentException', (Прекращение работы приложения из-за необработанного исключения) reason: ’*** -[Fraction noSuchMethod]: unrecognized selector sent to instance 0x103280’ (причина: ... нераспознанный селектор передан экземпляру) Stack: ( 2482717003, 2498756859, 2482746186, 2482739532, 2482739730 ) Trace/BPT trap

Чтобы избежать аварийного завершения программы, можно поместить оди н или несколько операторов в блоке операторов, имеющем следующий формат. @try { оператор оператор } @catch (NSException *exception) { оператор оператор )

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

В профамме 9.5 показана обработка исключения. Затем приводится вывод программы. #import "Fraction.h" int main {int arge, char *argv []) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *f = [[Fraction alloc] init]; @try { [f noSuchMethod]; } @catch (NSException *exception) { NSLog(@"Caught %@%@", [exception name], [exception reason]); } NSLog (@"Execution continues!"); [f release]; [pool drain]; return 0; }

Вывод программы 9.5 *** -[Fraction noSuchMethod]: unrecognized selector sent to instance 0x103280 Caught NSInvalidArgumentException: *** -[Fraction noSuchMethod]: unrecognized selector sent to instance 0x103280 Execution continues! (Выполнение продолжается!)

Если возникает исключительная ситуация, выполняется блок @catch. Объект NSExceptton, который содержит информацию об исключении, передастся в этот блок как аргумент. Мметод name считывает имя исключения, а метод reason указывает причину (которую система runtime раньше выводила автоматически). После выполнения последнего оператора в блоке @catch (здесь только один оператор) программа продолжает выполнение, начиная с оператора, который непосредственно следует за этим блоком. В данном случае мы выполняем вызов NSLog, чтобы подтвердить, что выполнение не было прекращено.

Это очень простой пример, показывающий, как перехватывать исключения в профамме. Можно также использовать блок @finally, чтобы включить код, выполняемый независимо от возникновения исключительной ситуации в каком– либо операторе блока @try.

Директива @throw позволяет создавать ваше собственное исключение. Ее можно использовать для создания конкретного исключения или внугри блока @catch для создания той же исключительной ситуации, которая вызвала переход в этот блок: @throw;

Это может потребоваться после вашей собственной обработки исключения (например, после выполнения операций очистки). После этого вы можете передать системе остальную часть работы. И, наконец, у вас может быть несколько блоков @catch, которые следуют в определенном порядке для перехвата и обработки исключений различного типа. Упражнения

Что произойдет, если вставить выражение с сообщением [compResult reduce]; в профамму 9.1 после того, как выполнено сложение (но до выполнения release для compResult)? Попробуйте и посмотрите, что получится.

Можно ли переменной dataValue типа id (определенной в профамме 9.2) присвоить объект класса Rectangle в соответствии с его определением в главе 8? Иначе говоря, является ли допустимым оператор dataValue = [[Rectangle alloc] init]; Почему?

Добавьте метод print к классу XYPoint, определенному в главе 8. Он должен выводить точку в формате (х,у). Затем внесите изменения в программу 9.2, чтобы включить объект типа XYPoint. Эта модифицированная программа должна создавать объект типа XYPoint, задавать его значение, присваивать его переменной dataValue типа id и затем выводить его значение.

Вспомните, что говорилось в этой главе о типах ар1ументов и возвращаемых значений, и модифицируйте методы add: в классах Fraction и Complex, чтобы принимать и возвращать объекты типа id. Затем напишите программу, которая включает следующую последовательность кода. result = [dataValuel add: dataValue2]; [result print];

Здесь result, dataValuel и dataValue2 – это объекты типа id. He забудьте задать образом значения dataValuel и dataValue2 в программе и освободить (release) все объекты, прежде чем завершить программу.

Примечание. Вам придется изменить имна этих методов. Системный класс NSObjectController тоже содержит метод add:. Как говорилось выше в разделе «Типы аргументов и возвращаемых значений при динамическом контроле типов», в случае существования нескольких методов с одним именем в разных классах, если на этапе компиляции тип получателя неизвестен, компилятор выполняет проверку согласованности типов аргументов и возвращаемого значения с методами, имеющими одинаковые имена.

Используя определения классов Fraction и Complex, заданные в этой книге, и определения Fraction *fraction = [[Fraction alloc] init]; Complex *complex = [[Complex alloc] init]; id number = [[Complex alloc] init]; определите возвращаемое значение для следующих выражений с сообщениями. Затем введите их в программу, чтобы проверить результаты. [fraction isMemberOfClass: [Complex class]]; [complex isMemberOfClass: [NSObject class]]; [complex isKindOfClass: [NSObject class]]; [fraction isKindOfClass: [Fraction class]]; [fraction respondsToSelector: @selector (print)]; [complex respondsToSelector: @selector (print)]; [Fraction instancesRespondToSelector: @selector (print)]; [number respondsToSelector: @selector (print)]; [number isKindOfClass: [Complex class]]; [number respondsToSelector: @selector (release)]; [[number class] respondsToSelector: @selector (alloc)];

Глава 10. Более подробно о переменных и типах данных

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

Перечислимый (enumerated) тип данных позволяет определять имя для типа данных, которое будет использоваться только для хранения заданного списка значений. В языке Objective-C оператор typedef позволяет вам назначать собственное имя встроенному или производному типу данных. В этой главе мы описываем действия компилятора по преобразованию типов данных при оценке выражений. 10.1. Инициализация классов

Мы уже встречали такой набор действий, когда выделяется память для нового экземпляра объекта, а затем выполняется его инициализация: Fraction *myFract = [[Fraction alloc] init];

После вызова этих методов обычно выполняется присваивание некоторых значений новому объекту: [myFract setTo: 1 over: 3];

Процесс инициализации объекта, после которого ему присваиваются начальные значения, часто объединяют в один метод. Например, можно определить метод initWith::, который инициализирует объект типа fraction (дробь) и присваивает два (неименованных) заданных аргумента его числителю (numerator) и знаменателю (denominator).

Класс, который содержит много методов и переменных экземпляра, обычно имеет несколько методов инициализации. Например, класс NSArray из Foundation framework содержит шесть методов инициализации. initWithArray: initWithArrayxopyltems: initWithContentsOfFile: initWithContentsOfURL: initWithObjects: initWithObjects:count:

Массиву (array) можно выделить память и затем инициализировать его, например, с помощью следующей последовательности: myArray = [[NSArray alloc] initWithArray: myOtherArray];

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

Если ваш класс содержит более одного инициализатора, один из них должен быть вашим назначенным (designated ) инициализатором, и все остальные методы инициализации должны его использовать. Обычно это более сложный метод инициализации (и принимает больше всего параметров). При создании назначенного инициализатора основной код инициализации объединяется в одном методе. При создании подкласса можно затем замещать назначенный инициализатор, чтобы обеспечить правильную инициализацию новых экземпляров. Необходимо следить за тем, чтобы правильно инициализировались любые наследуемые переменные экземпляра. Наиболее простой способ —вызывать сначала назначенный метод инициализации из родительского класса, который обычно называется init, а после этого инициализировать свои собственные переменные экземпляра.

Исходя из этого, метод инициализации initWith:: для класса Fraction может выглядеть следующим образом. -(Fraction *) initWith: (int) n: (int) d { self = [super init]; if (self) [self setTo: n over: d]; return self; }

Этот метод вызывает сначала родительский инициализатор, которым является метод init из NSObject (напомним, что это родительский класс для Fraction).

Вы должны присвоить результат self, поскольку инициализатор имеет право изменять или перемещать объект в памяти.

После инициализации super (и ее успешного завершения, что указывается ненулевым возвращаемым значением) используется метод setTo:over:, чтобы задать числитель (numerator) и знаменатель (denominator) дроби (Fraction). Как и для других методов инициализации, предполагается, что вы возвращаете инициализированный объект.

В программе 10.1 выполняется проверка нового метода инициализации initWith::. Программа 10.1 #import "Fraction.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *a, *b; a = [[Fraction alloc] initWith: 1: 3]; b = [[Fraction alloc] initWith: 3: 7]; [a print]; [b print]; [a release]; [b release]; [pool drain]; return 0; }

Вывод программы 10.1 1/3 3/7

Когда программа начинает выполнение, она передает метод вызова инициализации всем нашим классам. Если имеется класс и связанный с ним подкласс, родительский класс получает это сообщение первым. Это сообщение передается каждому классу только один раз, и оно гарантированно отправляется до того, как любые другие сообщения будут переданы классу. Ваша цель в этот момент – выполнение инициализации любого класса. Например, вам может потребоваться инициализация некоторых статических переменных, связанных с данным классом. 10.2. Снова об области действия

На область действия переменных в программе можно влиять разными способами: с переменными экземпляра или с обычными переменными, определенными вне или внутри функций. Ниже мы будем использовать термин модуль (module ) при ссылке на любое число определений методов или функций, содержащихся в одном исходном файле. Директивы для управления областью действия переменных экземпляра

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

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

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

@private. Методы, определенные в данном классе (но не в подклассах), могут выполнять непосредственный доступ к последующим переменным экземпляра.

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

@package. Для 64-битных образов доступ к переменной экземпляра может выполняться в любом месте образа, который реализует данный класс.

Если вам нужно определить класс с именем Printer, содержащий две частные переменные экземпляра с именами pageCount и tonerLevel, которые доступны только методам из класса Printer, то вы можете использовать следующую секцию interface. @interface Printer: NSObject { @ private int pageCount; int tonerLevel; @ protected // другие переменные экземпляра } @end

Доступ к этим двум переменным экземпляра нельзя выполнить из любого подкласса класса Printer, поскольку они сделаны частными (private).

Эти специальные директивы действуют как переключатели: все переменные, которые появляются после одной из этих директив (пока не появится правая фигурная скобка, которая являются концом объявлений этих переменных), имеют указанную область действия, если не использована другая директива. В приведенном примере директива @protected гарантирует, что следующие после нее переменные экземпляра будут доступны для методов подклассов и класса Printer.

Директива @public делает переменные экземпляра доступными для других методов или функций с помощью оператора-указателя (->), который описывается в главе 13. Такой доступ к переменным экземпляра не допускается практикой надежного программирования, поскольку он нарушает концепцию инкапсуляции данных (то есть скрытие классом своих переменных экземпляра). Внешние переменные

Если написать int gMoveNumber = 0;

в начале программы – вне любого метода, определения класса или функции, – то ее значение можно использовать из любого места данного модуля. В этом случае gMoveNumber определяется как гло б а ль н а я переменная. Обычно принято использовать букву g как первую букву глобальной переменной, чтобы обозначить для читателя программы ее область действия.

На самом деле такое определение переменной gMoveNumber делает ее значение доступным из других файлов. Приведенный оператор определяет переменную gMoveNumber не только как глобальную переменную, но и как внешнюю глобальную переменную.

Внешней (external) переменной является переменная, чье значение может изменяться другими методами или функциями. Внутри модуля, из которого требуется доступ к этой переменной, она объявляется обычным образом, перед ее объявлением ставится ключевое слово extern. Это указывает системе, что требуется доступ к глобально определенной переменной из другого файла. Ниже показан пример объявления переменной gMoveNumber как внешней переменной. extern int gMoveNumber;

Модуль, в котором появилось это объявление, может выполнять доступ к переменной gMoveNumber и изменять ее значение. Другие модули тоже могут выполнять доступ к значению gMoveNumber, используя в своем файле аналогичное объявление extern.

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

Второй способ определения внешней переменной – это объявление переменной вне любой функции с ключевым словом extern перед этим объявлением и явным присваиванием начального значения этой переменной, например, extern int gMoveNumber = 0;

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

При работе с внешними переменными можно объявлять переменную как extern во многих местах, но определить ее можно только один раз. Рассмотрим программу как пример использования внешних переменных. Мы определили класс с именем Foo и ввели следующий код в файл main.m: #import "Foo.h" int gGlobalVar = 5; int main (int argc, char *argc[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Foo *myFoo = [[Foo alloc] init]; NSLog (@"%i", gGlobalVar); [myFoo setgGlobalVar: 100] NSLog (@"%i", gGlobalVar); [myFoo release]; [pool drain]; return 0; }

Определение глобальной переменной gGlobalVar в этой программе делает ее значение доступным для любого метода (или функции), где используется соответствующее объявление extern. Предположим, что используется метод класса Foo с именем setgGlobalVar:. -(void) setgGlobalVar: (int) val { extern int gGlobalVar; gGlobalVar = val; }

Программа выведет следующие результаты. 5 100

Таким образом, метод setgGlobalVar: может выполнять доступ к внешней переменной gGlobalVar и изменять ее значение.

В тех случаях, когда доступ к значению gGlobalVar требуется многим методам, проще включить объявление extern только один раз в начале файла. Но если доступ к этой переменной требуется одному методу или небольшому числу методов, то имеет смысл включать объявления extern в каждый из таких методов; это сделает программу более организованной и ограничит использование конкретной переменной теми функциями, в которых она действительно требуется. Отметим, что если переменная определена внутри файла, содержащего код, который выполняет доступ к этой переменной, то отдельные объявления extern не требуются. Статические переменные

Только что показанный пример нарушает принцип инкапсуляции данных и практику надежного объектно-ориентированного программирования. Но вам может потребоваться работа с переменными, значения которых должны быть доступны для вызова из различных методов. Может показаться, что нет смысла делать переменную gGlobalVar переменной экземпляра в классе Foo, надежнее «скрыть» ее в классе Foo путем ограничения доступа к ней методами-установщиками (setter) и методами-получателями (getter), определенными для этого класса.

Теперь вы знаете, что любая переменная, определенная вне метода, является не только глобальной, но и внешней переменной. Но существует много ситуаций, когда нужно определить переменную, которая является глобальной, но не является внешней. Иначе говоря, вам нужно определить глобальную переменную, которая будет локальной для определенного модуля (файла). Имеет смысл определять такую переменную, если доступ к ней требуется только тем методам, которые содержатся в конкретном определении класса. Это можно сделать, определив переменную как статическую (static) внутри файла, содержащего секцию implementation для конкретного класса.

Если следующий оператор помещен вне любого метода (или функции), то значение gGlobalVar будет доступно из любой последующей точки файла, содержащей это определение, но будет недоступно из методов или функций, содержащихся в других файлах. static int gGlobalVar = 0;

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

В программе 10.2 определение класса Fraction расширяется за счет добавления двух новых методов. Метод класса allocF выделяется память для нового объекта типа Fraction и следит за числом дробей (объектов Fraction), которые он выделил, а метод count возвращает значение этого счетчика. Метод count тоже является методом класса. Его можно было бы реализовать как метод экземпляра, но лучше запросить класс, сколько экземпляров он выделил, вместо передачи сообщения определенному экземпляру этого класса.

Ниже приводятся объявления для двух новых методов класса, добавленные в файл Fraction.h. +(Fraction *) allocF; +(int) count;

Отметим, что здесь не замещается наследуемый метод alloc; вместо этого определяется наш собственный метод выделения памяти. В этом методе будет использоваться наследуемый метод alloc. Следующий код нужно поместить в файл секции implementation Fraction.m. static int gCounter; @implementation Fraction +(Fraction *) allocF { extern int gCounter; ++gCounter; return [Fraction alloc]; } +(int) count { extern int gCounter; return gCounter; } // здесь находятся другие методы из класса Fraction @end

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

Объявление static для переменной gCounter делает ее доступной для любого метода, определенного в секции implementation, но при этом она недоступна вне этого файла. Метод allocF просто наращивает значение переменной gCounter и затем использует метод alloc для создания новой дроби (Fraction), возвращая результат. Метод count просто возвращает значение счетчика (gCounter), не давая пользователю непосредственный доступ к этой переменной.

Напомним, что объявления extern не требуются в этих методах, поскольку переменная gCounter определена внутри этого файла. Это просто помогает читателю метода понять, что выполняется доступ к переменной, определенной вне метода. Префикс g в имени переменной предназначен для той же цели, поэтому большинство программистов обычно не включают объявления extern. В программе Ю.2 выполняется тестирование этих методов. #import "Fraction.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *a, *b, *c; NSLog (@"Fractions allocated: %i", [Fraction count]); a = [[Fraction allocF] init]; b = [[Fraction allocF] init]; c = [[Fraction allocF] init]; NSLog (@"Fractions allocated: %i", [Fraction count]); [a release]; [b release]; [c release]; [pool drain]; return 0; }

Вывод программы 10.2 Fractions allocated: 0 (Выделено объектов Fraction) Fractions allocated: 3

Когда начинается выполнение этой программы, значение gCounter автоматически задается равным 0 (напомним, что вы можете замещать наследуемый метод инициализации класса, если хотите выполнить специальную инициализацию класса в целом, например, присвоить статическим переменным некоторые ненулевые значения). После выделения (и последующей инициализации) трех объектов типа Fraction с помощью метода allocF метод count считывает переменную counter, значение которой действительно стало равным 3. Вы можете добавить метод-установщик (setter) для этого класса, если хотите выполнять сброс счетчика или задавать для него определенное значение, но в данном случае это не требуется. 10.3. Описатели хранения для класса


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

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