Язык программирования Objective-C
Программирование для Mac OS X Боресков Алексей
🕛 15.05.2009, 18:20
Помимо широкого известного и распространенного объектного расширения языка С - языка С++ - есть и другое его расширение - язык Objective-C, обладающий огромной простотой, полной совместимостью с языком С и очень мощной и выразительной объектной моделью, заимствованной из языка Smalltalk.Язык был придуман Брэдом Коксом (Brad Cox) в начале 80-х годов прошлого века. Целью Кокса было создание языка, поддерживающего концепцию software IC. Под этой концепцией понимается возможность собирать программы из готовых компонент (объектов), подобно тому как сложные электронные устройства могут быть легко собраны из набора готовых интегральных микросхем (IC, integrated curcuits).
При этом такой язык должен быть достаточно простым и основанным на языке С, чтобы облегчить переход разработчиков на него.
Одной из целей было также создание модели, в которой сами классы также являются полноценными объектами, поддерживалась бы интроспекция и динамическая обработка сообщений.
Получившийся в результате язык Objective-C оказался крайне прост - его освоение у С-программиста займет всего несколько дней. Он является именно расширением языка С - в язык С просто добавлены новые возможности для объектно-ориентированного программирования. При этом любая программа на С является программой и на Objective-C (для языка С++ это не верно).
Одной из отличительных черт Objective-C является его динамизм - целый ряд решений, обычно принимаемых на этапе компиляции, здесь откладывается непосредственно до этапа выполнения.
Еще одной из особенностей языка является то, что он message-oriented в то время как С++ - function-oriented. Это значит, что в нем вызовы метода интерпретируются не как вызов функции (хотя к этому обычно все сводится), а именно как посылка сообщения (с именем и аргументами) объекту, подобно тому, как это происходит в Smalltalk-е.
Такой подход дает целый ряд плюсов - так любому объекту можно послать любое сообщение. Объект может вместо обработки сообщения просто переслать его другому объекту для обработки (так называемое делегирование), в частности именно так можно легко реализовать распределенные объекты (т.е. объекты находящиеся в различных адресных пространствах и даже на разных компьютерах).
Привязка сообщения к соответствующей функции происходит непосредственно на этапе выполнения.
Язык Objective-C поддерживает нормальную работу с метаинформацией - так у объекта непосредственно на этапе выполнения можно спросить его класс, список методов (с типами передаваемых аргументов) и instance-переменных, проверить, является ли класс потомком заданного и поддерживает ли он заданный протокол и т.п.
В языке есть нормальная поддержка протоколов (т.е. понятие интерфейса объекта и протокола четко разделены). Для объектов поддерживается наследование (не множественное), для протоколов поддерживается множественное наследование. Объект может быть унаследован от другого объекта и сразу нескольких протоколов (хотя это скорее не наследование протокола, а его поддержка).
На данный момент язык Objective-C поддерживается компилятором gcc (соответственно для форточек он поддерживается mingw и cygwin). Судя по заявлениям в новой версии Mac OS X 10.5 будет кроме gcc (используемого для всех предыдущих версий) еще и компилятор от Intel'а. Также по слухам в этой версии операционной системы будет использован Objective-C 2, куда в частности войдет поддержка сборки мусора (garbage collection)/
Довольно много в языке перенесено на runtime-библиотеку и сильно зависит от нее. Вместе с компилятором gcc поставляется минимальный вариант такой библиотеки. Также можно свободно скачать runtime-библиотеку от компании Apple: Apple's Objective-C runtime.
Эти две runtime-библиотеки довольно похожи (в основном отличие заключается в именах методов), хотя далее я буду ориентироваться на runtime-библиотеку от компании Apple.
Синтаксис языка
В языке Objective-C для обозначения объектов используется специальный тип id (да, именно то самое id, стоящее в названии idSoftware). Переменная типа id фактически является указателем на произвольный объект. Для обозначения нулевого указателя на объект используется константа nil.
При этом вместо id можно использовать и более привычное обозначение с явным указанием класса. В частности последнее позволяет компилятору осуществлять некоторую проверку поддержки сообщения объектами - если компилятор из типа переменной не может сделать вывод о поддержке объектом данного сообщения, то он выдаст предупреждение (а не ошибку !).
Тем самым язык поддерживает проверку типов, но в нестрогой форме (т.е. найденные несоответствия возвращаются как предупреждения, а не ошибки).
Для посылки сообщений используется следующий синтаксис:
[receiver message];
В этой конструкции receiver является указателем на объект, а message - именем метода.
В отличии от языка С++ посылка сообщения nil'у является законной операцией, всегда возвращающей нулевое значение (nil).
Сообщение может также содержать параметры:
[myRect setOrigin:30.0 :50.0];
В этом примере именем метода (сообщения) является setOrigin::. Обратите внимание, что каждому передаваемому аргументу соответствует ровно одно двоеточие. При этом в приведенном примере первый аргумент имеет метку (текст перед двоеточием), а второй - нет.
Язык Objective-C позволяет снабжать метками каждый аргумент, что заметно повышает читаемость кода и снижает вероятность передачи неправильного параметра.
[myRect setWidth:10.0 height:20.0];
В этом примере в качестве имени сообщения выступает setWidth:height:.
Также поддерживается возможность передачи произвольного количества аргументов в сообщении:
[myObject makeGroup: obj1, obj2, obj3, obj4, nil];
Как и функции, сообщения могут возвращать значения, при этом в отличии от языка С, типом возвращаемым по умолчанию значения является id.
float area = [myRect area];
Результат одного сообщения можно сразу же использовать в другом сообщении:
[myRect setColor:[otherRect color]];
Как уже говорилось, в Objective-C классы сами являются объектами. Основной задачей таких объектов (называемых class objects) является создание экземпляров данного класса (это очень похоже на паттерн Abstract Factory).
При этом само имя класса играет двойную роль - с одной стороны оно выступает как тип данных (т.е. он может быть использован для описания указателей на объекты данного класса). А с другой стороны имя класса может выступать в качестве объекта, которому посылается сообщение ( в сообщениях имя класса может принимать участие только как receiver).
Rect * myRect = [[Rect alloc] init]];
В языке Objective-C нет встроенного типа для булевский величин, поэтому обычно такой тип вводится искусственно. Далее я буду для логических величин использовать тип BOOL с возможными значениями YES и NO (как это делается в операционных системах NextStep, Mac OS X).
Первым достаточно серьезным применением языка Objective-C было его использование в операционной системе NextStep. Для этой системы было написано большое количество различных классов на Objective-C, многие из которых до сих пор используются в Mac OS X.
Имена всех этих классов начинаются с префикса NS, обозначающего свою принадлежность к операционной системе NextStep.
С одним из таких классов - NSString - мы столкнемся в данной статье. Этот класс служит для работы со строками (при этом в качестве внутреннего представления символов используется Юникод).
Компилятор поддерживает данный тип, автоматически переводя конструкции вида @"my string" в указатель на объект класса NSString, содержащий данную строку (точнее его подкласса, соответствующего константным строкам).
Создание новых классов
Все новые директивы компилятору в языке Objective-C начинаются с символа @.
Как и в С++ описание класса и его реализация разделены (обычно описание помещается в заголовочные файлы с расширением h, а реализации - в файлы с расширением m).
Ниже приводится общая структура описания нового класса:
@interface ClassName : SuperClass
{ instance variable declarations
}
method declarations
@end
В версии runtime от Apple все классы имеют общего предка - класс NSObject, содержащий целый ряд важных методов.
Описание переменных ничем не отличается от описания переменных в структурах в языке С:
@interface Rect : NSObject { float width; float height; BOOL isFilled; NSColor * color; } @end
Описания же методов заметно отличаются от принятых в С++ и очень сильно похожи на описания методов в языке Smalltalk.
Каждое описание начинается со знака плюс или минус. Знак плюс обозначает, что данный метод является методом класса (т.е. его можно посылать только class object'у, а не экземплярам данного класса). Фактически методы класса являются аналогами статических методов в классах в языке С++.
Знак минус служит для обозначения методов объектов - экземпляров данного класса. Обратите внимание, что в Objective-C все методы являются виртуальными, т.е. могут быть переопределены.
Ниже приводятся описания возможных методов для класса Rect.
@interface Rect : NSObject { float x, y; float width; float height; BOOL isFilled; NSColor * color; } + newRect; - (void) display; - (float) width; - (float) height; - (float) area; - (void) setWidth: (float) theWidth; - (void) setHeight: (float) theHeight; - (void) setX: (float) theX y: (float) theY; @end
Обратите внимание, что имя метода может совпадать с именем instance-переменной данного класса (например, width и heigh).
Тип возвращаемого методом значения указывается в круглых скобках сразу же после знака плюс или минус (но перед названием метода). Если тип не указан, то считается, что возвращается значение типа id.
Далее идет имя метода, где после каждого двоеточия задается тип аргумента (в круглых скобках) и сам аргумент.
Язык Objective-C позволяет для аргументов метода задавать также один из следующих описателей - oneway, in, out, inout, bycopy и byref. Данные описатели служат для задания направления передачи данных и способа передачи. Их наличие заметно упрощает реализацию и работу с распределенными объектами (которые были реализованы в операционной системе NextStep к началу 90-х годов прошлого века).
Метод, принимающий произвольное количество параметров, может быть описан следующим образом:
- makeGroup: (id) object, ...;
Для подключения заголовочного файла в Objective-C вместо директивы #include используется директива #import, полностью аналогичная #include, но гарантирующая что данных файл будет подключен всего один раз.
В ряде случаев возникает необходимость в объявлении того, что данное имя является именем класса, но без явного его описания (такая необходимость возникает при описании двух классов, каждый из которых ссылается на другой класс).
В этом случае можно воспользоваться директивой @class, объявляющей, что следующие за ней имена являются именами классов.
@class Shape, Rect, Oval;
Реализация методов класса выглядит следующим образом:
#import "ClassName.h" @implmentation ClassName method implementations @end
Ниже приводится пример реализации методов класса Rect, описанного выше.
#import "Rect.h" @implmentation Rect + newRect { Rect * rect = [[Rect alloc] init]; [rect setWidth: 1.0f]; [rect setHeight: 1.0f]; [rect setX: 0.0f y: 0.0f]; } - (float) width { return width; } - (float) height { return height; } - (float) area { return [self width] * [self height]; } - (void) setWidth: (float) theWidth { width = theWidth; } - (void) setHeight: (float) theHeight { height = theHeight; } - (void) setX: (float) theX y: (float) theY { x = theX; y = theY; } @end
Как видно из примера выше, в методах доступны все instance-переменные. Однако, как и в С++, есть возможность управлять видимостью переменных (видимостью методов управлять нельзя) при помощи директив @private, @protected и @public (действующих полностью аналогично языку С++).
@interface Worker : NSObject { char * name; @private int age; char * evaluation; @protected int job; float wage; @public id boss } При этом к public переменным класса можно обращаться непосредственно использую оператор -> (например objPtr -> fieldName). Как работает механизм сообщений Компилятор переводит каждую посылку сообщения, т.е. конструкцию вида [object msg] в вызов функции objc_msgSend. Эта функция в качестве своего первого параметра принимает указатель на объект-получатель сообщения, в качестве второго параметра выступает т.н. селектор, служащий для идентификации посылаемого сообщения. Если в сообщении присутствуют аргументы, то они также передаются objc_msgSend как третий, четвертый и т.д. параметры. Каждый объект Objective-C содержит в себе атрибут isa - указатель на class object для данного объекта. class object автоматически создается компилятором и существует как один экземпляр, на который через isa ссылаются все экземпляры данного класса. Каждый class object обязательно содержит в себе указатель на class object для родительского класса (superclass) и dispatch table. Последняя представляет из себя словарь, сопоставляющий селекторам сообщений фактические адреса реализующих их методов (функций). Т.о. функция objc_msgSend ищет метод с данным селектором в dispatch table для данного объекта. Если его там нет, то поиск продолжается в dispatch table для его родительского класса и т.д. shapes class diagram Диаграмма для классов (со следующим порядком наследования Rectangle:Shape:NSObject) Если метод (т.е. соответствующая ему функция) находится, то осуществляется его вызов с передачей всех необходимых аргументов. В противном случае объекту дается последний шанс обработать сообщение перед вызовом исключения - селектор сообщения вместе с параметрами "заворачивается" в специальный объект типа NSInvocation и объекту посылается сообщение forwardInvocation:, где в качестве параметра выступает объект класса NSInvocation. Если объект поддерживает forwardInvocation:, то он может либо сам обработать посылаемое сообщение, либо переслать другому объекту для обработки: - (void)forwardInvocation:(NSInvocation *)anInvocation { if ( [someOtherObject respondsToSelector: [anInvocation selector]] ) [anInvocation invokeWithTarget: someOtherObject]; else . }
Для ускорения поиска сообщений по dispatch table используется кэширование, позволяющее заметно снизить затраты на пересылку сообщений. Стоит заметить, что язык Objective-C активно применялся для построения пользовательского интерфейса и библиотеки классов для операционной системы NextStep, в то время когда эта система работа на компьютерах с 25-Мгц процессорами Motorol 68030.
Если даже на таком процессоре язык обеспечивал приемлемое быстродействие, то сейчас о нем можно практически не думать.
Для облегчения поиска метода по таблицам в Objective-C вместо имен методов используются так называемые селекторы. Обычно селектор представляет собой 32-битовую величину, позволяющую однозначно идентифицировать метод.
Тип селектора обозначается как SEL и существует ряд функций и конструкций, позволяющих осуществлять преобразование имени в селектор и обратно.
Так для получения селектора сообщения непосредственно по имени служит конструкция @selector():
SEL setWidth = @selector(setWidth:); SEL setPos = @selector(setX:y:);
Для получения селектора по строке символов (на этапе выполнения) и перевода селектора в строку служат функции NSSelectorFromString и NSStringFromSelector:
SEL setWidth = NSSelectorFromString ( @"setWidth:" ); NSString * methodName = NSStringForSelector ( setPos );
Мощная поддержка метаинформации в Objective-C позволяет прямо на этапе выполнения проверить поддерживает ли объект метод с данным селектором при помощи посылки ему сообщения respondsToSelector::
if ( [anObject respondsToSelector: @selector(setWidth:)] ) [anObject setWidth: 200.0];
Довольно легко можно послать сообщение, соответствующее данному селектору (без аргументов, с одним, двумя или тремя аргументами) при помощи метода performSelector:, performSelector:withObject:, performSelector:withObject:withObject: и performSelector::withObject:withObject::withObject:.
[myObject performSelector:sel withObject: nil];
Обратите внимание, что методы performSelector: всегда возвращают значение типа id.
Можно получить класс для данного объекта, послав ему сообщение class. Это сообщение возвращает класс в виде указателя на объект типа Class.
Class * cls = [anObject class]; NSString * clsName = NSStringFromClass ( cls );
С другой стороны также можно легко получить соответствующий class object по имени класса:
Class * cls = NSClassFromString ( clsName );
Каждый метод фактически представляет собой функцию с двумя невидимыми аргументами - self и _cmd.
Первый является аналогом this, т.е. указывает на сам объект - получатель сообщения. Второй - содержит селектор данного метода.
Аргумент self может использоваться для посылки сообщений самому себе, как например в следующем методе:
- (float) area
{ return [self width] * [self height];
}
Однако кроме self есть еще одна величина, которой могут посылаться сообщения - super. На самом деле super не является нормальной переменной - это всего лишь еще одно обозначение для указателя на текущий объект. Но при посылке сообщения super поиск метода начинается не с dispatch table текущего объекта, а с dispatch table родительского объекта.
sending messages to self and super
Рис. 2. Разница при посылке сообщения self и super (для объекта класса Rectangle).
Таким образом, посылая сообщения super мы тем самым вызываем старые версии методов, переопределенные данным классом.
В отличии от языка С++ (в котором несмотря на то, что всем известно, что метод - это просто функция с дополнительным параметром this, нет никакого официально документированного способа получить указатель на эту функцию) в языке Objective-C можно по селектору метода получить адрес реализующей его функции (именно как функции языка С).
Такая функция отличается от описания метода только вставкой в начала списка аргументов двух дополнительных параметров - указателя на сам объект (self) и селектора данного метода (_cmd).
Послав объекту сообщение methodForSelector: мы получаем в ответ адрес реализующей этот метод функции.
typedef float (*WidthFunc)( id, SEL );
typedef void (*SetWidthFunc)( id, SEL, float );
WidthFunc widthFunc = (WidthFunc) [myRect methodForSelector: @selector (width)];
SetWidthFunc setWidthFunc = (SetWidthFunc) [myRect methodForSelector: @selector (setWidth:)];
(*setWidthFunc)( myRect, @selector (setWidth:), 27.5f );
Это позволяет при необходимости многократного вызова одного и того же метода у заданного объекта полностью избежать всех расходов, связанных с пересылкой сообщений.
Протоколы
Язык Objective-C содержит полноценную поддержку протоколов. Протокол представляет из себя просто список описаний методов. Объект реализует протокол, если он содержит реализации всех методов, описанных в протоколе.
Протоколы удобны тем, что позволяют выделять общие черты у разнородных объектов и передавать информацию об объектах заранее неизвестных классов.
Простейшее описание протокола выглядит следующим образом:
@protocol ProtocolName
method declarations
@end
Так протокол Serializable может быть описан следующим образом:
@protocol Serializable
- (id) initWithCoder: (NSCoder *) coder;
- (void) encodeWithCoder: (NSCoder *) coder;
@end
Протокол может быть унаследован от произвольного количества других протоколов:
@protocol MyProto <Protocol1, Protocol2, Serializable,Drawable>
Точно также можно при описании класса задать не только родительский класс, но и набор протоколов:
@interface MyClass : SuperClass <Protocol1, Protocol2, Serializable,Drawable>
Для проверки во время выполнения программы поддерживается ли объектом заданный протокол объектов можно использовать сообщение conformsToProtocol::
if ( [myObject conformsToProtocol: @protocol (Serializable)] ) [myObject encodeWithCoder: myCoder];
Кроме того имя протокола (протоколов) можно использовать при описании переменных для явного указания компилятору о поддержке соответствующими объектами протокола (протоколов).
Так если переменная myObject содержит указатель на объект заранее неизвестного класса, но при этом удовлетворяющий протоколам Serializable и Drawable, то ее можно описать следующим образом:
id <Serializable,Drawable> myObject;
Точно также, если заранее известно, что myObject будет содержать указатель на объект, унаследованный от класса Shape и поддерживающего протокол Serializable, то эту переменную можно описать следующим образом:
Shape <Serializable> myObject;
Обратите внимание, что подобное описание служит только для сообщения компилятору какие сообщения поддерживает данный объект.
Как и классы, все протоколы в Objective-C представлены при помощи объектов (класса Protocol):
Protocol * myProto = @protocol ( Serializable );
Для предварительного объявления протоколов можно использовать следующую конструкцию:
@protocol MyProto, Serializable, Drawable;
Это конструкция сообщает компилятору о том, что MyProto, Serializable и Drawable являются именами протоколов, которые будут определены позже.
Обработка исключений
В языке Objective-C поддерживается обработка исключений очень похожая на используемую в языках C++ и Java.
Для этого служат директивы @try, @catch, @finally и @throw.
Cup * cup = [[Cup alloc] init];
@try
{ [cup fill];
}
@catch ( NSException * exc )
{ NSLog ( @"Exception caught: %@", exc );
}
@catch ( id ex )
{ NSLog ( @"Unknown exception caught" );
}
@finally
{ [cup release];
}
Для запуска исключения используется директива @throw, в качестве аргумента берущая указатель на объект-исключение. Обычно в Mac OS X/NextStep для этой цели используются объекты класса NSException.
NSException * exc = [NSException exceptionWithName: @"my-exception" reason: @"unknown-error" userInfo: nil];
@throw exc;
Внутри @catch-блоков директива @throw может использоваться без параметра для повторного запуска обрабатываемого исключение (rethrowing exception).
Синхронизация
Язык Objective-C поддерживает синхронизацию для многопоточных приложений. При помощи директивы @synchronized () можно защитить фрагмент кода от одновременного выполнения сразу несколькими нитями.
@synchronized () берет на вход указатель на объект языка Objective-C (можно использовать для этой цели любой объект, в том числе и self), который играет роль мьютекса(mutex).
При попытке нити начать выполнение защищенного фрагмента проверяется не выполняется ли уже этот фрагмент какой-либо нитью. Если да, то сравниваются объекты, переданные этими нитями в @synchronized ().
Если эти указатели различаются, то нить, пытающаяся войти в защищенный блок будет приостановлена (suspended) до тех пор, пока первая нить не выйдет из блока. Тогда выполнение второй нити продолжится и уже она "запрет" этот блок для всех остальных нитей.
Наличие подобной возможности заметно облегчает жизнь при написании многонитевых приложений, когда необходимо отслеживать попытки одновременного изменения одних и тех же данных сразу несколькими нитями.
- (void) criticalMethod
{ @synchronized ( self ) { // perfrom modifications to shared objects . . . }
}
Создание и уничтожение объектов
В самом языке Objective-C нет специальных команд для создания и уничтожения объектов (подобных new и delete). Эта задача ложится на runtime-библиотеку и реализуется при помощи механизма посылки сообщений.
Реально используемой и наиболее широко распространенной схемой создания и уничтожения объектов в Objective-C является используемая в операционных системах NextStep и Mac OS X, которая и будет описана ниже.
Создание нового объекта разбивается на два шага - выделение памяти и инициализация объекта. Первый шаг реализуется методом класса alloc (реализованном в классе NSObject), который выделяет необходимое количество памяти (данный метод используется для выделения памяти не только для объектов класса NSObject, но и любого унаследованного от него класса). При этом выделяемая память обнуляется и в атрибут isa записывается указатель на class object соответствующего класса.
Обратите внимание, что сообщение alloc посылается class object-у требуемого класса и это сообщение возвращает указатель на выделенную под объект память.
Собственно сама инициализация объекта (т.е. установка значений его instance-переменных, выделение дополнительных ресурсов и т.п.) осуществляется другими методами, по традиции имена этих методов начинаются с init. Обычно такое сообщение посылается сразу же после сообщение alloc, по адресу, возвращенному этим сообщением.
id anObject = [[Rectangle alloc] init];
Приведенная выше конструкция является правильным способом создания объекта. Обратите внимание, что следующая конструкция может в ряде случаев не работать:
id anObject = [Rectangle alloc];
[anObject init];
Это связано с тем, что для ряда классов метод init может вернуть совсем другой указатель (а не self).
Простейшими примерами того, когда может возникать подобная ситуация, являются синглетоны (тогда, если один экземпляр класса уже существует, то метод init освободит выделенную alloc'ом память и вернет указатель на уже созданный единственный экземпляр) и кэширование объектов, когда для увеличения производительности, выделение объектов происходит сразу блоками и объекты не уничтожаются, а сохраняются для переиспользования.
При создании нового класса обычно нет необходимости переопределять метод alloc, а вот необходимость переопределения метода init возникает достаточно часто (хотя во многих случаях можно положится на обнуление памяти alloc'ом).
Обратите внимание, что метод(ы) init является обычным методом, ничем не выделяющимся среди остальных (в отличии от С++, где конструктор - это особый метод, у которого например нельзя взять адрес).
Поэтому при создании нового класса и метода init вызов переопределенного метода init (при помощи [super init]) должен быть произведен явно в самом начале метода.
Довольно часто у объектов бывает сразу несколько методов, начинающихся с init, например init, initWithName:, initWIthContentsOfFile: и т.д.
Установившейся практикой в таком случае является выделение среди всех init-методов одного, называемого designated initializer. Все остальные init-методы должны вызывать его и только он вызывает унаследованный init метод.
- initWithName: (const char *) theName // designated initializer
{ [super init]; // call inherited method
name = strdup ( theName );
}
- init
{ return [self initWithName: ""];
}
В ряде случаев оказывается удобным совместить выделение памяти и инициализацию объекта в один метод (класса), например в классе NSString есть ряд методов класса, возвращающих уже готовый (проинициализированный) объект:
+ (NSString *) initStringWithCString: (const char *) str;
+ (NSString *) initStringWithFormat: (NSString *) format, ...;
Mac OS X (как и NextStep) для управления временем жизни объектов используют reference counting - каждый объект содержит внутри себя некоторый счетчик, при создании устанавливаемый в единицу.
Посылка объекту сообщения retain увеличивает значение этого счетчика на единицу (так все контейнерные классы библиотеки Foundation при помещении в них объекта, посылают ему сообщение retain).
Установившейся практикой является посылка объекту сообщения retain всеми, заинтересованными в нем сторонами (объектами), т.е. если вы запоминаете ссылку на объект, то следует послать ему сообщение retain.
Когда объект перестает быть нужен, то ему просто посылается сообщение release.
Данное сообщение уменьшает значение счетчика на единицу и, если это значение стало меньше единицы, уничтожает данный объект.
Перед уничтожением объекта ему посылается сообщение dealloc, позволяющее объекту произвести свою деинициализацию. При этом это также является обычным сообщением и в нем Вы явно должны в конце вызвать унаследованную реализацию через [super dealloc].
- (void) dealloc
{ . . . [super dealloc];
}
Категории
Язык Objective-C обладает крайне редко встречающейся возможностью добавлять новые методы к уже существующим классам. При этом не требуется исходников класса и добавленные методы автоматически становятся доступными всем классам, унаследованным от изменяемого.
Так можно добавить новый метод классу NSObject и этот метод автоматические добавится во все остальные классы !
Аналогичной возможностью обладает язык Ruby, но Objective-C является компилируемым языком, а Ruby - интерпретируемым.
Механизм, позволяющий расширять уже существующие классы (путем добавление новых методов, новые instance-переменные добавить таким образом нельзя), называется категориями.
Категория имеет свое имя, список методов и имя класса, который она расширяет. Описание категории имеет следующий вид:
#import "ClassName.h"
@interface ClassName ( CategoryName ) methods declarations
@end
Реализация категории выглядит следующим образом:
#import "CategoryName.h"
@interface ClassName ( CategoryName ) methods bodies
@end
Class objects и Objective-C runtime
При компиляции программы на Objective-C компилятор для каждого введенного класса автоматически создает так называемый class object - полноценный объект, содержащий в себе всю информацию о данном классе, включая название, суперкласс, список методов и instance-переменных.
При этом такой объект является полноценным объектом, т. е. ему можно посылать сообщения, передавать в качестве параметра.
Одной из особенностью class object'а является то, что он поддерживает все методы класса NSObject, т.е. когда ему посылается сообщение, то сначала идет поиск среди методов класса, а если метод не найден, то поиск продолжается среди instance-методов класса NSObject.
Еще одной интересной особенностью является возможность инициализации class object'ов - в начале работы приложения каждому class object'у посылается сообщение (класса) initialize.
Это сообщение гарантированно посылается каждому class object'у, причем всего один раз и до того, как ему будет послано любое другое сообщение. Простейшим примером применения такого сообщения является реализация Singleton'ов - именно в методе initialize следует создать тот самый единственный экземпляр объекта и запомнить его в static-переменной.
Если посмотреть на Objective-C runtime от Apple, то мы найдем большое количество С-функция, служащих для работы с классами (непосредственно во время выполнения программы).
Наиболее интересными являются следующие:
Method class_getInstanceMethod( Class aClass, SEL aSelector );
Method class_getClassMethod ( Class aClass, SEL aSelector );
struct objc_method_list * class_nextMethodList(Class theClass, void ** iterator);
void class_addMethods ( Class aClass, struct objc_method_list * methodList );
void class_removeMethods ( Class aClass, struct objc_method_list * methodList );
unsigned method_getNumberOfArguments ( Method method );
unsigned method_getSizeOfArguments ( Method method );
unsigned method_getArgumentInfo ( Method method, int argIndex, const char ** type, int * offset );
Ivar class_getInstanceVariable ( Class aClass, const char * aVariableName );
Функция class_getInstanceMethod возвращает указатель на структуру (objc_method) описывающую заданный instance-метод данного класса.
Функция class_getClassMethod возвращает указатель на структуру (objc_method) описывающую заданный метод данного класса.
Функция class_nextMethodList возвращает один из списков методов для заданного класса. Приводимый ниже фрагмент кода позволяет перебрать все методы для данного класса.
void * iterator = 0; struct objc_method_list * mlist;
// // Each call to class_nextMethodList returns one methodList //
methodList = class_nextMethodList( classObject, &iterator )
while ( methodList != NULL ) { // …do something with the method list here…
methodList = class_nextMethodList ( classObject, &iterator ); }
Функция class_addMethods позволяет добавлять новые методы к заданному классу.
Функция class_removeMethods позволяет убирать методы из заданного класса.
Функция method_getNumberOfArguments Возвращает количество аргументов для заданного метода.
Функция method_getSizeOfArguments возвращает размер места на стеке, занимаемого всему аргументами данного метода.
Функция method_getArgumentInfo возвращает информацию об одном из аргументов для заданного метода..
Функция class_getInstanceVariable возвращает информацию об instance-переменной класса в виде указателя на структуру objc_ivar.
Для кодирования информации о типах используется специальное строковое представление, однозначно сопоставляющее каждому типу данных некоторую строку. Явно получить такую строку для произвольного типа можно при помощи конструкции @encode ().
char * buf1 = @encode ( int ** );
char * buf2 = @encode ( struct key );
char * buf3 = @encode ( Rectangle );
Objective-C 2.0
В Mac OS X 10.5 Leopard вошла новая вресия языкы - Objective-C 2.0, в которую было добавлено много элементов, упрощающих использование языка. В частности именно эта версия (2.0) используется для разработки приложений под платформу iPhone/iPod Touch.
Одним из наиболее значительный изменений является опциональная поддержка сборки мусора (garbage collection). Для подключения этой возможности достаточно просто задать в свойствах проекта флаг использования сборки мусора. Т.е. это необязательная опция - каждый сам может решать стоит ли ему ее использовать или нет. Кроме того, любое корректно написанное приложение (используующее библиотеки Apple), работающее без garbage collection, будучи откомпилированным с этой опцией, также будет полностью корректно работать. Следует правда отметить, что для разработки приложений под платформу iPhone/iPod Touch garbage collection пока не поддерживается.
Также в Objective-C 2.0 вошел более привычный синтаксис для доступа к свойствмм (properties) объектов, используя точку как для доступа к полям структуры в C/C++/Java.
Пусть у нас есть класс Person, описанный ниже.
#import <Cocoa/Cocoa.h>
@interface Person : NSObject
{ NSString * firstName; NSString * lastName;
}
- (NSString *) firstName;
- (void) setFirstName: (NSString *) newFirstName;
- (NSString *) lastName;
- (void) setLastName: (NSString *) newFirstName;
@end
Тогда, если у нас переменная bill является указателем на объект класс Person, то следующие конструкции для доступа к полям (свойствам) этого объекта полностью законны.
NSLog ( @"First name is %@", bill.firstName );
bill.firstName = @"Bill"
bill.lastName = @"Gates";
При этом на самом деле подобный синтаксис является всего лишь syntax sugar и переводится компиляторм в нормальный с использованием set/get-методов:
NSLog ( @"First name is %@", [bill firstName] );
[bill setFirstName: @"Bill]"
[bill setLastName: @"Gates"];
Очень удобной возможностью Objective-C 2.0 стала поддержка автоматического создания set/get-методов. Реализации этих методов обычно крайне просты и однообразны, но отнимают время и силы программиста. Поэтому эта работа легко может быть переложена на компилятор.
Для этого необходимо в разделе @interface описать каждое свойство, методы доступа к которому должно быть созданы при помощи директивы @property, а затем в разделе @implmentation использовать директиву @synthesize или @dynamic.
Директива @property имеет следующую структуру:
@property (attributes) type name;
Через type и name обозначены тип значения и имя свойства. В круглых скобках задается список атрибутов, задающих колмпилятору какие именно set/get-методы нужно создать.
Доупстимы следующие атрибуты:
* getter=getterName - явно задать имя get-метода; * setter=setterName - явно задать имя set-метода; * readonly - для данного свойства следуент создать только get-метод; * readwrite - для данного свойства следует создать и set- и get-методы (по умолчнию); * assign - set-метод делает простое присваивание значений; * retain - set-метод делает присваивание значений с использованием retain/release; * copy - set-метод делает простое присваивание копии исходного значения; * atomic - обеспечить атомарность доступа (имеет смысл только для многонитевых приложений); * nonatomic - не надо обеспечивать атомарность (по умолчанию).
Обратите внимание, что опции readonly/readwrite являются взаимоисключающими. Также взаимоисключающими является опции assign/retain/copy.
Ниже приводятся реализации set-метода для каждого из трех опций - assign/retain/copy.
// assign
- (void) setFirstName: (NSString *) newName
{ firstName = newName;
}
// retain
- (void) setFirstName: (NSString *) newName
{ if ( firstName != newName ) { [firstName release]; firstName = [newName retain]; }
}
// copy
- (void) setFirstName: (NSString *) newName
{ if ( firstName != newName ) { [firstName release]; firstName = [newName copy]; }
}
Обратите внимание, что для коректной работы со счетчиками числа ссылок при работе с объектами следует использовать либо retain, либо copy. При этом в первом случае будет просвоена ссылка на тот же объект, а в последнем - ссылка на независмо существующую копию.
С использованием директивы @property описание класса Person может выглядеть следующим образом:
#import <Cocoa/Cocoa.h>
@interface Person : NSObject
{ NSString * firstName; NSString * lastName;
}
@property (nonatamic,retain) NSString * firstName;
@property (nonatomic,retain) NSString * lastName;
@end
Для создания реализации set/get-методов в разделе @implmentation следует использовать директиву @synthesize:
#import "Person.h"
@implementation Person
- (void) dealloc
{ [firstName autorelease]; [lastName autorelease];
}
@synthesize firstName;
@synthesize lastName;
@end
Вместо директивы @synthesize можно использовать директиву @dynamic (по умолчнию), говорящую, что set/get-методы Вы зададите сами.
Еще одним нововведением языка Objective-C 2.0 стало упрощенная форма цикла по содержимому контейнера. Так если у нас array - это объект класса, унаследованного от NSArray, а dict - объект класса, унаследованного от NSDictionary, то для перебора элементов в них можно использовать следующие фрагменты кода:
for ( NSString * str in array ) NSLog ( @"Element: %@", str );
for ( id key in dict ) NSLog ( @"Key: %@ Value: %@", id, [dict valueForKey: key] );
Для того, чтобы можно было подобным образом итерировать по контейнеру он должен поддерживать протокол NSFastEnumaertion (все стандартные контейнеры его поддерживают). Большим блюсом подобного перебора элементов является то, что во время перебора любая попытка изменить контейнер приведет в выбрасыванию исключения, т.е. это безопасный перебор.
Разное
Крайне полезным (и пожалуй главным) источником информации по языку является сайт developer.apple.com.
Также ряд полезной информации по языку Objective-C можно найти в news-группе comp.lang.objective-c.
Проект GNUstep представляет собой попытку создания open-source библиотек на Objective-C, являющихся аналогом библиотек Foundation и AppKit в NextStep и Mac OS X.
Весьма полезный перевод статьи Objective-C для программистов C++.
На сайте проекта GNUstep Вы найдете много примеров использование языка Objective-C и приложений, написанных на нем.
Поскольку компилятор gcc поддерживает язык Objective-C, то практически каждый дистрибутив Linux позволяет установить поддержку для него.
Для работы с Objective-C под M$ Windows можно использовать компиляторы mingw и cygwin, необходимо только установить библиотеку для поддержки языка.
В качестве редактора, поддерживающего выделение синтаксиса для Objective-C можно использовать бесплатный notepad++.
Официальный comp.lang.objective-C FAQ.
В работе над статьей были использованы материалы с developer.apple.com.