Objective-C: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
[непроверенная версия][непроверенная версия]
Содержимое удалено Содержимое добавлено
викификация
 
(не показано 360 промежуточных версий, сделанных более чем 100 участниками)
Строка 1: Строка 1:
{{Карточка языка программирования
'''Objective-C''', известный также как '''Objective C''', '''ObjC''' или '''Obj-C''' — [[компиляция|компилируемый]] [[объектно-ориентированное программирование|объектно-ориентированный]] [[язык программирования]], построенный на основе [[Си (язык программирования)|языка C]].
| name = Objective-C
| paradigm = [[Объектно-ориентированное программирование|объектно-ориентированный]], [[Мультипарадигмальный язык программирования|мультипарадигмальный]]: [[Отражение (программирование)|рефлексивно-ориентированный]]
| year = 1983
| designer = [[Кокс, Брэд|Брэд Кокс]]
| latest release version = [[Objective-C 2.0]]
| typing = [[Сильная и слабая типизация|слабая]], [[статическая типизация|статическая]] / [[динамическая типизация|динамическая]]
| implementations = [[Cocoa]], [[Cocoa Touch]], [[GNU Compiler Collection|gcc]], [[Low Level Virtual Machine|LLVM]] + [[Clang]]
| influenced_by = [[Smalltalk]], [[Си (язык программирования)|C]]
| influenced = [[Java]], [[Objective-J]], [[Swift (язык программирования)|Swift]]
| сайт = https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html
| operating_system = [[Cross-platform]]
}}
'''Objective-C''' — [[Компиляция (программирование)|компилируемый]] [[объектно-ориентированное программирование|объектно-ориентированный]] [[язык программирования]], используемый корпорацией [[Apple]], построенный на основе языка [[Си (язык программирования)|Си]] и парадигм [[Smalltalk]]. В частности, объектная модель построена в стиле [[Smalltalk]] — то есть объектам ''посылаются сообщения''.


Язык Objective-C является надмножеством языка [[Си (язык программирования)|Си]], поэтому Си-код полностью понятен компилятору Objective-C.
В отличие от [[Си плюс плюс|C++]], язык '''Objective-C''' полностью совместим с [[Си (язык программирования)|Си]] и является довольно тонкой надстройкой. Объектная модель построена в стиле [[Smalltalk]], то есть, объектам посылаются ''сообщения''.


Компилятор '''Objective-C''' входит в [[GCC]] и доступен на большинстве основных
Компилятор Objective-C входит в [[GCC]] и доступен на большинстве основных платформ. Язык используется в первую очередь для [[macOS]] ([[Cocoa]]) и [[GNUstep]] — реализаций объектно-ориентированного интерфейса [[OpenStep]]. Также язык используется для [[Apple iOS|iOS]] ([[Cocoa Touch]]).
платформ. Язык используется в первую очередь для [[Mac OS X]] ([[Cocoa (API)|Cocoa]]) и [[GNUstep]] — двух реализаций объектно-ориентированного стандарта операционной системы [[OpenStep]].


==История==
== История ==
В начале [[1980-е|1980-х годов]] было популярно [[структурное программирование]], позволяющее разделить алгоритм на небольшие блоки. Однако, с ростом сложности задач, структурное программирование приводило к снижению качества кода. Приходилось писать всё больше функций, которые очень редко могли использоваться в других программах.


Многие программисты увидели в объектно-ориентированном программировании потенциальное решение возникшей проблемы. С одной стороны, [[Smalltalk]] использовали почти все более-менее сложные системы. С другой — использование виртуальных машин повышало требования к ресурсам.
В начале 1980х годов было распространено структурное программирование. Оно было разработано для того, чтобы «разбивать» программу на малые части, в основном, чтобы было легче работать над программами, исходный код которых увеличивался в размерах. Однако вставали более сложные задачи, с решением которых структурное программирование плохо справлялось. Приходилось писать всё больше функций, которые очень редко могли использоваться в других программах.


Objective-C был создан [[Кокс, Брэд|Брэдом Коксом]] в начале 1980-х в его компании [[Stepstone]]. Он пытался решить проблему повторного использования кода.
Многие увидели в объектно-ориентированном программировании потенциальное решение возникшей проблемы. С одной стороны, [[Smalltalk]] использовали почти все более-менее сложные системы. С другой — использование виртуальных машин сильно тормозило работу системы и требовало огромных ресурсов.


Целью Кокса было создание языка, поддерживающего концепцию software IC, подразумевающей возможность собирать программы из готовых компонентов (объектов), подобно тому как сложные электронные устройства могут быть собраны из набора готовых [[Интегральная схема|интегральных микросхем]].
ObjC был создан Брэдом Коксом ([[Brad Cox]]) в начале 1980х в его компании [[Stepstone]]. Он был заинтересован в решении проблемы повторного использования кода.


При этом язык должен быть простым и основанным на языке С, чтобы облегчить переход разработчиков на него.
Целью Кокса было создание языка, поддерживающего концепцию software IC. Под этой концепцией понимается возможность собирать программы из готовых компонент (объектов), подобно тому как сложные электронные устройства могут быть легко собраны из набора готовых интегральных микросхем (''IC, integrated curcuits'').


Одной из целей было также создание модели, в которой сами классы являются полноценными объектами, поддерживалась бы [[Интроспекция (программирование)|интроспекция]] и динамическая обработка сообщений.
При этом такой язык должен быть достаточно простым и основанным на языке С, чтобы облегчить переход разработчиков на него.


Objective-C является расширением С: любая программа на С является программой на Objective-C.
Одной из целей было также создание модели, в которой сами классы также являются полноценными объектами, поддерживалась бы интроспекция и динамическая обработка сообщений.


Одной из отличительных черт Objective-C является динамичность: решения, обычно принимаемые на этапе компиляции, здесь откладываются до этапа выполнения.
Получившийся в результате язык Objective-C оказался крайне прост - его освоение у С-программиста займет всего несколько дней. Он является именно расширением языка С - в язык С просто добавлены новые возможности для объектно-ориентированного програмирования. При этом любая программа на С является программой и на Objective-C (для языка С++ это не верно).


Objective-C — message-oriented-язык, в то время как C++ — function-oriented: в Objective-C вызовы метода интерпретируются не как вызов функции (хотя к этому обычно все сводится), а как посылка сообщения (с именем и аргументами) объекту, подобно тому, как это происходит в Smalltalk.
Одной из отличительных черт Objective-C является его динамизм - целый ряд решений, обычно принимаемых на этапе компиляции, здесь откладывается непосредственно до этапа выполнения.


Любому объекту можно послать любое сообщение. Объект может вместо обработки сообщения переслать его другому объекту для обработки (делегирование), в частности, так можно реализовать распределённые (то есть находящиеся в различных адресных пространствах и даже на разных компьютерах) объекты.
Еще одной из особеностей языка является то, что он message-oriented в то время как С++ - function-oriented. Это значит, что в нем вызовы метода интерпретируются не как вызов функции (хотя к этому обычно все сводится), а именно как посылка сообщения (с именем и аргументами) объекту, подобно тому, как это происходит в Smalltalk-е.


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


Язык Objective-C поддерживает работу с [[Метаданные|метаинформацией]] — так, на этапе выполнения можно узнать класс объекта, список его методов (с типами передаваемых аргументов) и instance-переменных, проверить, является ли класс потомком заданного и поддерживает ли он заданный протокол и т. п.
Привязка сообщения к соответствующей функции происходит непосредственно на этапе выполнения.


В языке есть поддержка протоколов (понятия интерфейса объекта и протокола четко разделены). Поддерживается наследование (не множественное); для протоколов поддерживается множественное наследование. Объект может быть унаследован от другого объекта и сразу нескольких протоколов (хотя это скорее не наследование протокола, а его поддержка).
Язык Objective-C поддерживает нормальную работу с метаинформацией - так у объекта непосредственно на этапе выполнения можно спросить его класс, список методов (с типами передаваемых аргументов) и instance-переменных, проверить, является ли класс потомком заданного и поддерживает ли он заданный протокол и т.п.


На данный момент язык Objective-C поддерживается компиляторами [[Clang]] и GCC (под управлением [[Windows]] используется в составе [[MinGW]] или [[cygwin]]).
В языке есть нормальная поддержка протоколов (т.е. понятие интерфеса объекта и протокола четко разделены). Для объектов поддерживается наследование (не множественное), для протоколов поддерживается множественное наследование. Объект может быть унаследован от другого объекта и сразу нескольких протоколов (хотя это скорее не наследование протокола, а его поддержка).


Некоторые функции языка перенесены в [[runtime]]-библиотеку и сильно зависят от неё. Вместе с компилятором gcc поставляется минимальный вариант такой библиотеки. Также можно свободно скачать runtime-библиотеку компании Apple: Apple’s Objective-C runtime.
На данный момент язык Objective-C поддерживается компилятором gcc (соответственно для форточек он поддежвается mingw и cygwin).
Довольно много в языке перенесено на runtime-библиотеку и сильно зависит от нее. Вместе с компилятором gcc поставляется минимальный вариант такой библиотеки. Также можно свободно скачать runtime-библиотеку от компании Apple: Apple's Objective-C runtime.

Эти две runtime-библиотеки довольно похожи (в основном отличие заключается в именах методов), хотя далее я буду ориентироваться на runtime-библиотеку от компании Apple.


Эти две runtime-библиотеки похожи (основные отличия в именах методов). Далее примеры будут ориентироваться на runtime-библиотеку Apple.


== Синтаксис языка ==
== Синтаксис языка ==
В языке Objective-C для обозначения объектов используется специальный тип id (это аналог типа Object в [[Java]]). Переменная типа id фактически является указателем на произвольный объект. Для обозначения нулевого указателя на объект используется константа nil (= NULL).


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


Тем самым язык поддерживает проверку типов, но в нестрогой форме (то есть найденные несоответствия возвращаются как предупреждения, а не ошибки).
В языке Objective-C для обозначения объектов используется специальный тип id (да, именно то самое id, стоящее в названии idSoftware). Переменная типа id фактически является указателем на произвольный объект. Для обозначения нулевого указателя на объект используется константа nil.

При этом вместо id можно использовать и более привычное обозначение с явным указанием класса. В частности последнее позволяет компилятору осуществлять некоторую проверку поддержки сообщения объектами - если компилятор из типа переменной не может сделать вывод о поддержке объектом данного сообщения, то он выдаст предупреждение (а не ошибку !).

Тем самым язык поддерживает проверку типов, но в нестрогой форме (т.е. найденные несоответствия возвращаются как предупреждения, а не ошибки).


Для посылки сообщений используется следующий синтаксис:
Для посылки сообщений используется следующий синтаксис:
<source lang="objc">

[receiver message];
[receiver message];
</source>
В этой конструкции ''receiver'' является указателем на объект, а ''message'' — именем метода.


В отличие от языка C++, посылка сообщения nil’у является законной операцией, всегда возвращающей нулевое значение (nil).
В этой конструкции receiver является указателем на объект, а message - именем метода.

В отличии от языка С++ посылка сообщения nil'у является законной операцией, всегда возвращающей нулевое значение (nil).


Сообщение может также содержать параметры:
Сообщение может также содержать параметры:
<source lang="objc">

[myRect setOrigin:30.0 :50.0];
[myRect setOrigin:30.0 :50.0];
</source>
В этом примере именем метода (сообщения) является setOrigin::. Обратите внимание, что каждому передаваемому аргументу соответствует ровно одно двоеточие. При этом в приведенном примере первый аргумент имеет метку (текст перед двоеточием), а второй — нет.


Язык Objective-C позволяет снабжать метками каждый аргумент, что заметно повышает читаемость кода и снижает вероятность передачи неправильного параметра. Именно такой стиль принят большинством разработчиков.
В этом примере именем метода (сообщения) является setOrigin::. Обратите внимание, что каждому передаваемому аргументу соответствует ровно одно двоеточние. При этом в приведенном примере первый аргумент имеет метку (текст перед двоеточием), а второй - нет.
<source lang="objc">

Язык Objective-C позволяет снабжать метками каждый аргумент, что заметно повышает читаемость кода и снижает вероятность передачи неправильного параметра.

[myRect setWidth:10.0 height:20.0];
[myRect setWidth:10.0 height:20.0];
</source>

В этом примере в качестве имени сообщения выступает setWidth:height:.
В этом примере в качестве имени сообщения выступает setWidth: height:.


Также поддерживается возможность передачи произвольного количества аргументов в сообщении:
Также поддерживается возможность передачи произвольного количества аргументов в сообщении:
<source lang="objc">

[myObject makeGroup: obj1, obj2, obj3, obj4, nil];
[myObject makeGroup: obj1, obj2, obj3, obj4, nil];
</source>

Как и функции, сообщения могут возвращать значения, при этом в отличии от языка С, типом возвращаемым по умолчанию значения является id.
Как и функции, сообщения могут возвращать значения, при этом в отличие от языка С, типом значения, возвращаемым по умолчанию, является id.
<source lang="objc">

float area = [myRect area];
float area = [myRect area];
</source>

Результат одного сообщения можно сразу же использовать в другом сообщении:
Результат одного сообщения можно сразу же использовать в другом сообщении:
<source lang="objc">

[myRect setColor:[otherRect color]];
[myRect setColor:[otherRect color]];
</source>
Как уже говорилось, в Objective-C классы сами являются объектами. Основной задачей таких объектов (называемых class objects) является создание экземпляров данного класса (паттерн [[Фабричный метод (шаблон проектирования)|Factory method]])<ref>{{Cite web|url=https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#//apple_ref/doc/uid/TP40011210-CH3-SW1|title=Defining Classes|author=|website=|subtitle=Objective-C Classes Are also Objects|quote=The typical use for a class method is as a factory method|date=|publisher=developer.apple.com|accessdate=2019-12-21|archive-date=2020-11-12|archive-url=https://web.archive.org/web/20201112020038/https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#//apple_ref/doc/uid/TP40011210-CH3-SW1|deadlink=no}}</ref>.


При этом само имя класса играет двойную роль — с одной стороны оно выступает как тип данных (то есть он может быть использован для описания указателей на объекты данного класса). А с другой стороны имя класса может выступать в качестве объекта, которому посылается сообщение (в сообщениях имя класса может принимать участие только как receiver).
Как уже говорилось, в Objective-C классы сами являются объектами. Основной задачей таких объектов (называемых class objects) является создание экземпляров данного класса (это очень похоже на паттерн Abstract Factory).
<source lang="objc">
Rect * myRect = [[Rect alloc] init];
</source>
В языке Objective-C нет встроенного типа для булевских величин, поэтому обычно такой тип вводится искусственно. Далее для логических величин будет использоваться тип BOOL с возможными значениями YES и NO (как это делается в операционных системах NextStep, Mac OS X).


Первым достаточно серьёзным применением языка Objective-C было его использование в операционной системе [[NeXTSTEP|NextStep]]. Для этой системы было написано большое количество различных классов на Objective-C, многие из которых до сих пор используются в Mac OS X.
При этом само имя класса играет двойную роль - с одной стороны оно выступает как тип данных (т.е. он может быть использован для описания указателей на объекты данного класса). А с другой стороны имя класса может выступать в качестве объекта, которому посылается сообщение ( в сообщениях имя класса может принимать участие только как receiver).


Имена всех этих классов начинаются с префикса NS, обозначающего свою принадлежность к операционной системе NextStep. Сейчас они входят в библиотеку Foundation, на которой строятся приложения для OS X и iOS.
Rect * myRect = [[Rect alloc] init]];


С одним из них — NSString — мы столкнемся в данной статье. Этот класс служит для работы со строками (при этом в качестве внутреннего представления символов используется Юникод).
В языке Objective-C нет встроенного типа для булевский величин, поэтому обычно такой тип вводится искусственно. Далее я буду для логических величин использовать тип BOOL с возможными значениями YES и NO (как это делается в операционных системах NextStep, Mac OS X).


Компилятор поддерживает данный тип, автоматически переводя конструкции вида @"my string" в [[Указатель (тип данных)|указатель]] на объект класса NSString, содержащий данную строку (точнее его подкласса, соответствующего константным строкам).
Первым достаточно серьезным применением языка Objective-C было его использование в операционной системе NextStep. Для этой системы было написано большое количество разлиных классов на Objective-C, многие из которых до сих пор используются в Mac OS X.


;Свойства
Имена всех этих классов начинаются с префикса NS, обозначающего свою принадлежность к операционной системе NextStep.
Допустим, в классе Company существует instance-переменная name.
<source lang="objc">
@interface Company : NSObject
{
NSString *name;
}
</source>
Для доступа к ней извне лучше всего воспользоваться [[Свойство (программирование)|свойствами]], которые появились в Objective-C 2.0. Для объявления свойств используется ключевое слово @property.
<source lang="objc">
@property (retain) NSString *name;
</source>
В скобках перечисляются атрибуты доступа к instance-переменной. Атрибуты разделяются на 3 основные группы.


'''Имена акцессора и мутатора'''
С одним из таких классов - NSString - мы столкнемся в данной статье. Этот класс служит для работы со строками (при этом в качестве внутреннего предствления символов используется Юникод).
* getter=getterName, используется для задания имени функции, используемой для извлечения значения instance-переменной.

* setter=setterName, используется для задания имени функции, используемой для установки значения instance-переменной.
Компилятор поддерживает данный тип, автоматически переводя конструкции вида @"my string" в указатель на объект класса NSString, содержащий данную строку (точнее его подкласса, соответствующего константным строкам).
'''Ограничение чтения/записи'''
* readwrite — у свойства есть как акцессор, так и мутатор. Является атрибутом по умолчанию.
* readonly — у свойства есть только акцессор.
Эти атрибуты взаимоисключают друг друга.
И последняя группа '''атрибуты мутатора'''.
* assign — для задания нового значения используется оператор присваивания. Используется только для [[Plain Old Data Structures|POD-типов]] либо для объектов, которыми мы не владеем.
* retain — указывает на то, что для объекта, используемого в качестве нового значения instance-переменной, управление памятью происходит вручную (не забываем потом освободить память).
* copy — указывает на то, что для присваивания будет использована копия переданного объекта.
* weak — аналог assign при применении режима [[Automatic Reference Counting|автоматического подсчёта ссылок]]. (ARC должен поддерживаться компилятором)
* strong — аналог retain при применении режима [[Automatic Reference Counting|автоматического подсчёта ссылок]]. (ARC должен поддерживаться компилятором)
При работе под [[Сборка мусора (программирование)|GC]] никакой разницы между использованием assign, retain, copy нет.
Для создания кода свойств, в соответствии с тем, как они описаны в объявлении, можно воспользоваться автогенерацией кода:
<source lang="objc">
@synthesize name;
</source>
Автоматически созданный код — не всегда подходящее решение и может потребоваться создание методов доступа к instance-переменным вручную.


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


== Создание новых классов ==
== Создание новых классов ==
Все ключевые слова языка Objective-C, отсутствующие в С, начинаются с символа @.


Как и в C++, описание класса и его реализация разделены (обычно описание помещается в заголовочные файлы с расширением h, а реализации — в файлы с расширением m).

Все новые директивы компилятору в языке Objective-C начинаются с символа @.

Как и в С++ описание класса и его реализация разделены (обычно описание помещается в заголовочные файлы с расширением h, а реализации - в файлы с расширением m).


Ниже приводится общая структура описания нового класса:
Ниже приводится общая структура описания нового класса:
<source lang="objc">

@interface ClassName : SuperClass
@interface ClassName : SuperClass
{
{
Строка 114: Строка 156:
method declarations
method declarations
@end
@end
</source>
В версии runtime от Apple все классы имеют общего предка — класс NSObject, содержащий целый ряд важных методов.


В версии runtime от Apple все классы имеют общего предка - класс NSObject, содержащий целый ряд важных методов.
Описание переменных ничем не отличается от описания переменных в структурах в языке С:
Описание переменных ничем не отличается от описания переменных в структурах в языке С:


Если у вас не Apple, то скорее всего вместо NSObject вам потребуется Object (#import &lt;objc/Object.h&gt;).
<source lang="objc">
@interface Rect : NSObject
@interface Rect : NSObject
{
{
Строка 126: Строка 171:
}
}
@end
@end
</source>
Описания же методов заметно отличаются от принятых в C++ и очень сильно похожи на описания методов в языке Smalltalk.


Каждое описание начинается со знака плюс или минус. Знак плюс обозначает, что данный метод является методом класса (то есть его можно посылать только class object’у, а не экземплярам данного класса). Фактически методы класса являются аналогами статических методов в классах в языке C++.
Описания же методов заметно отличаются от принятых в С++ и очень сильно похожи на описания методов в языке Smalltalk.


Знак минус служит для обозначения методов объектов — экземпляров данного класса. Обратите внимание, что в Objective-C все методы являются [[Виртуальный метод|виртуальными]], то есть могут быть переопределены.
Каждое описание начинается со знака плюс или минус. Знак плюс обозначает, что данный метод является методом класса (т.е. его можно посылать только class object'у, а не экземплярам данного класса). Фактически методы класса являются аналогами статических методов в классах вязыке С++.

Знак минус служит для обозначения методов объектов - экзмепляров данного класса. Обратите внимание, что в Objective-C все методы являются виртуальными, т.е. могут быть переопределены.


Ниже приводятся описания возможных методов для класса Rect.
Ниже приводятся описания возможных методов для класса Rect.
<source lang="objc">

@interface Rect : NSObject
@interface Rect : NSObject
{
{
Строка 152: Строка 197:
- (void) setX: (float) theX y: (float) theY;
- (void) setX: (float) theX y: (float) theY;
@end
@end
</source>

Обратите внимание, что имя метода может совпадать с имененм instance-переменной данного класса (например, width и heigh).
Обратите внимание, что имя метода может совпадать с именем instance-переменной данного класса (например, width и height).


Тип возвращаемого методом значения указывается в круглых скобках сразу же после знака плюс или минус (но перед названием метода). Если тип не указан, то считается, что возвращается значение типа id.
Тип возвращаемого методом значения указывается в круглых скобках сразу же после знака плюс или минус (но перед названием метода). Если тип не указан, то считается, что возвращается значение типа id.
Строка 159: Строка 204:
Далее идет имя метода, где после каждого двоеточия задается тип аргумента (в круглых скобках) и сам аргумент.
Далее идет имя метода, где после каждого двоеточия задается тип аргумента (в круглых скобках) и сам аргумент.


Язык Objective-C позволяет для аргументов метода задавать также один из следующих описателей - oneway, in, out, inout, bycopy и byref. Данные описатели служат для задания направления передачи данных и способа передачи. Их наличие заметно упрощает реализацию и работу с распределенными объектами (которые были реализованы в операционной системе NextStep к началу 90-х годов прошлого века).
Язык Objective-C позволяет для аргументов метода задавать также один из следующих описателей — oneway, in, out, inout, bycopy и byref. Данные описатели служат для задания направления передачи данных и способа передачи. Их наличие заметно упрощает реализацию и работу с распределенными объектами (которые были реализованы в операционной системе NextStep к началу 90-х годов прошлого века).


Метод, принимающий произвольное количество параметров, может быть описан следующим образом:
Метод, принимающий произвольное количество параметров, может быть описан следующим образом:
<source lang="objc">

- makeGroup: (id) object, ...;
- makeGroup: (id) object, ...;
</source>

Для подключения заголовочного файла в Objective-C вместо директивы #include используется директива #import, полностью аналагичная #include, но гарантирующая что данных файл будет подключен всего один раз.
Для подключения заголовочного файла в Objective-C вместо директивы #include используется [[Директива (программирование)|директива]] #import, аналогичная #include, но гарантирующая, что данный файл будет подключен всего один раз.


В ряде случаев возникает необходимость в объявлении того, что данное имя является именем класса, но без явного его описания (такая необходимость возникает при описании двух классов, каждый из которых ссылается на другой класс).
В ряде случаев возникает необходимость в объявлении того, что данное имя является именем класса, но без явного его описания (такая необходимость возникает при описании двух классов, каждый из которых ссылается на другой класс).


В этом случае можно воспользоваться директивой @class, объявляющей, что следующие за ней имена являются именами классов.
В этом случае можно воспользоваться директивой @class, объявляющей, что следующие за ней имена являются именами классов.
<source lang="objc">

@class Shape, Rect, Oval;
@class Shape, Rect, Oval;
</source>

Реализация методов класса выглядит следующим образом:
Реализация методов класса выглядит следующим образом:
<source lang="objc">

#import "ClassName.h"
#import "ClassName.h"


@implmentation ClassName
@implementation ClassName


method implementations
method implementations


@end
@end
</source>

Ниже приводится пример реализации методов класса Rect, описанного выше.
Ниже приводится пример реализации методов класса Rect, описанного выше.
<source lang="objc">

#import "Rect.h"
#import "Rect.h"


@implmentation Rect
@implementation Rect


+ newRect
+ newRect
Строка 196: Строка 241:
[rect setHeight: 1.0f];
[rect setHeight: 1.0f];
[rect setX: 0.0f y: 0.0f];
[rect setX: 0.0f y: 0.0f];

return rect;
}
}


Строка 230: Строка 277:


@end
@end
</source>

Как видно из примера выше, в методах доступны все instance-переменные. Однако, как и в С++, есть возможность управлять видимостью переменных (видимостью методов управлять нельзя) при помощи директив @private, @protected и @public (действующих полность аналогично языку С++).
Как видно из примера выше, в методах доступны все instance-переменные. Однако, как и в C++, есть возможность управлять видимостью переменных (видимостью методов управлять нельзя) при помощи директив @private, @protected и @public (действующих полностью аналогично языку C++).
<source lang="objc">

@interface Worker : NSObject
@interface Worker : NSObject
{
{
Строка 245: Строка 292:
id boss
id boss
}
}
</source>

При этом к public переменным класса можно обращаться непосредственно использую оператор -> (например objPtr -> fieldName).
При этом к public переменным класса можно обращаться непосредственно, используя оператор -> (например objPtr -> fieldName).



== Как работает механизм сообщений ==
== Как работает механизм сообщений ==
Компилятор переводит каждую посылку сообщения, то есть конструкцию вида [object msg] в вызов функции objc_msgSend. Эта функция в качестве своего первого параметра принимает указатель на объект-получатель сообщения, в качестве второго параметра выступает т. н. селектор, служащий для идентификации посылаемого сообщения. Если в сообщении присутствуют аргументы, то они также передаются objc_msgSend как третий, четвёртый и т. д. параметры.


Каждый объект Objective-C содержит в себе атрибут isa — указатель на class object для данного объекта. class object автоматически создается компилятором и существует как один экземпляр, на который через isa ссылаются все экземпляры данного класса.


Каждый class object обязательно содержит в себе указатель на class object для родительского класса (superclass) и dispatch table. Последняя представляет собой словарь, сопоставляющий селекторам сообщений фактические адреса реализующих их методов (функций).
Компилятор переводит каждую посылку сообщения, т.е. конструкцию вида [object msg] в вызов функции objc_msgSend.
Эта функция в качестве своего первого параметра принимает указатель на объект-получатель сообщения, в качестве второго параметра выступает т.н. селектор, служащий для идентификации посылаемого сообщения. Если в сообщении присутствуют аргументы, то они также передаются objc_msgSend как третий, четвертый и т.д. параметры.


Таким образом, функция objc_msgSend ищет метод с данным селектором в dispatch table для данного объекта. Если его там нет, то поиск продолжается в dispatch table для его родительского класса и т. д.
Каждый объект Objective-C содержит в себе атрибут isa - указатель на class object для данного объекта. class object автоматически создается компилятором и существует как один экземпляр, на который через isa ссылаются все экземпляры данного класса.


Если метод (то есть соответствующая ему функция) находится, то осуществляется его вызов с передачей всех необходимых аргументов.
Каждый class object обязательно содержит в себе указатель на class object для родительского класса (superclass) и dispatch table. Последняя представляет из себя словарь, сопоставляющий селекторам сообщений фактические адреса реализующих их методов (функций).


В противном случае объекту дается последний шанс обработать сообщение перед вызовом исключения — селектор сообщения вместе с параметрами «заворачивается» в специальный объект типа NSInvocation и объекту посылается сообщение forwardInvocation:, где в качестве параметра выступает объект класса NSInvocation.
Т.о. функция objc_msgSend ищет метод с данным селектором в dispatch table для данного объекта. Если его там нет, то поиск продолжается в dispatch table для его родительского класса и т.д.

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

В противном случае объекту дается последний шанс обработать сообщение перед вызовом исключения - селектор сообщения вместе с параметрами "заворачивается" в специальный объект типа NSInvocation и объекту посылается сообщение forwardInvocation:, где в качестве параметра выступает объект класса NSInvocation.


Если объект поддерживает forwardInvocation:, то он может либо сам обработать посылаемое сообщение, либо переслать другому объекту для обработки:
Если объект поддерживает forwardInvocation:, то он может либо сам обработать посылаемое сообщение, либо переслать другому объекту для обработки:
<source lang="objc">

- (void)forwardInvocation:(NSInvocation *)anInvocation
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
{
Строка 274: Строка 317:
..........
..........
}
}
</source>

Для ускорения поиска сообщений по dispatch table используется кэширование, позволяющее заметно снизить затраты на пересылку сообщений. Стоит заметить, что язык Objective-C активно применялся для построения пользовательского интерфейса и библиотеки классов для операционной системы NextStep, в то время когда эта система работа на компьютерах с 25-Мгц процессорами Motorol 68030.
Для ускорения поиска сообщений по dispatch table используется кэширование, позволяющее заметно снизить затраты на пересылку сообщений. Также облегчает поиск метода по таблицам использование так называемых селекторов вместо обычных имен. Обычно селектор представляет собой 32-битовую величину, позволяющую однозначно идентифицировать метод.

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

Для облегчения поиска метода по таблицам в Objective-C вместо имен методов используются так называемые селекторы. Обычно селектор представляет собой 32-битовую величину, позволяющую однозначно идентифицировать метод.


Тип селектора обозначается как SEL и существует ряд функций и конструкций, позволяющих осуществлять преобразование имени в селектор и обратно.
Тип селектора обозначается как SEL и существует ряд функций и конструкций, позволяющих осуществлять преобразование имени в селектор и обратно.


Так для получения селектора сообщения непосредственно по имени служит конструкция @selector():
Так для получения селектора сообщения непосредственно по имени служит конструкция @selector():
<source lang="objc">

SEL setWidth = @selector(setWidth:);
SEL setWidth = @selector(setWidth:);
SEL setPos = @selector(setX:y:);
SEL setPos = @selector(setPosition:y:);
</source>

Для получения селектора по строке символов (на этапе выполения) и перевода селектора в строку служат функции NSSelectorFromString и NSStringFromSelector:
Для получения селектора по строке символов (на этапе выполнения) и перевода селектора в строку служат функции NSSelectorFromString и NSStringFromSelector:
<source lang="objc">

SEL setWidth = NSSelectorFromString ( @"setWidth:" );
SEL setWidth = NSSelectorFromString ( @"setWidth:" );
NSString * methodName = NSStringForSelector ( setPos );
NSString * methodName = NSStringFromSelector ( setPos );
</source>

Мощная поддержка метаинформации в Objective-C позволяет прямо на этапе выполения проверить поддерживает ли объект метод с данным селектором при помощи посылки ему сообщения respondsToSelector::
Мощная поддержка метаинформации в Objective-C позволяет прямо на этапе выполнения проверить поддерживает ли объект метод с данным селектором при помощи посылки ему сообщения respondsToSelector::
<source lang="objc">

if ( [anObject respondsToSelector: @selector(setWidth:)] )
if ( [anObject respondsToSelector: @selector(setWidth:)] )
[anObject setWidth: 200.0];
[anObject setWidth: 200.0];
</source>

Довольно легко можно послать сообщение, соответствующее данному селектору (без аргументов, с одним, двумя или тремя аргументами) при помощи метода performSelector:, performSelector:withObject:, performSelector:withObject:withObject: и performSelector::withObject:withObject::withObject:.
Довольно легко можно послать сообщение, соответствующее данному селектору (без аргументов, с одним, двумя или тремя аргументами), при помощи метода performSelector:, performSelector: withObject:, performSelector: withObject: withObject:, performSelector: withObject: withObject: withObject: и так далее.
<source lang="objc">

[myObject performSelector:sel withObject: nil];
[myObject performSelector:sel withObject: nil];
</source>

Обратите внимание, что методы performSelector: всегда возвращают значение типа id.
Обратите внимание, что методы performSelector: всегда возвращают значение типа id.


Можно получить класс для данного объекта, послав ему сообщение class. Это сообщение возвращает класс в виде указателя на объект типа Class.
Можно получить класс для данного объекта, послав ему сообщение class. Это сообщение возвращает класс в виде указателя на объект типа Class.
<source lang="objc">

Class * cls = [anObject class];
Class * cls = [anObject class];
NSString * clsName = NSStringFromClass ( cls );
NSString * clsName = NSStringFromClass ( cls );
</source>

С другой стороны также можно легко получить соответствующий class object по имени класса:
С другой стороны также можно легко получить соответствующий class object по имени класса:
<source lang="objc">

Class * cls = NSClassFromString ( clsName );
Class * cls = NSClassFromString ( clsName );
</source>
Каждый метод фактически представляет собой функцию с двумя невидимыми аргументами — self и _cmd.


Первый является аналогом this, то есть указывает на сам объект — получатель сообщения. Второй — содержит селектор данного метода.
Каждый метод фактически представляет собой функцию с двумя невидимыми аргументами - self и _cmd.

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


Аргумент self может использоваться для посылки сообщений самому себе, как например в следующем методе:
Аргумент self может использоваться для посылки сообщений самому себе, как например в следующем методе:
<source lang="objc">

- (float) area
- (float) area
{
{
return [self width] * [self height];
return [self width] * [self height];
}
}
</source>

Однако кроме self есть еще одна величина, которой могут посылаться сообщения - super. На самом деле super не является нормальной переменной - это всего лишь еще одно обозначение для указателя на текущий объект. Но при посылке сообщения super поиск метода начинается не с dispatch table текущего объекта, а с dispatch table родительского объекта.
Однако кроме self есть ещё одна величина, которой могут посылаться сообщения — super. На самом деле super не является нормальной переменной — это всего лишь ещё одно обозначение для указателя на текущий объект. Но при посылке сообщения super поиск метода начинается не с dispatch table текущего объекта, а с dispatch table родительского объекта.


Таким образом, посылая сообщения super мы тем самым вызываем старые версии методов, переопределенные данным классом.
Таким образом, посылая сообщения super мы тем самым вызываем старые версии методов, переопределенные данным классом.


В отличии от языка С++ (в котором несмотря на то, что всем известно, что метод - это просто функция с дополнительным параметром this, нет никакого официально документированного способа получить указатель на эту функцию) в языке Objective-C можно по селектору метода получить адрес реализующей его функции (именно как функции языка С).
В языке Objective-C можно по селектору метода получить адрес реализующей его функции (именно как функции языка С).


Такая функция отличается от описания метода только вставкой в начала списка аргументов двух дополнительных параметров - указателя на сам объект (self) и селектора данного метода (_cmd).
Такая функция отличается от описания метода только вставкой в начало списка аргументов двух дополнительных параметров — указателя на сам объект (self) и селектора данного метода (_cmd).


Послав объекту сообщение methodForSelector: мы получаем в ответ адрес реализующей этот метод функции.
Послав объекту сообщение methodForSelector: мы получаем в ответ адрес реализующей этот метод функции.
<source lang="objc">

typedef float (*WidthFunc)( id, SEL );
typedef float (*WidthFunc)( id, SEL );
typedef void (*SetWidthFunc)( id, SEL, float );
typedef void (*SetWidthFunc)( id, SEL, float );
Строка 341: Строка 380:


(*setWidthFunc)( myRect, @selector (setWidth:), 27.5f );
(*setWidthFunc)( myRect, @selector (setWidth:), 27.5f );
</source>

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



== Протоколы ==
== Протоколы ==
Язык Objective-C содержит полноценную поддержку протоколов (это аналог интерфейса в Java и абстрактного класса в C++, который также иногда принято называть интерфейсом). Протокол представляет собой просто список описаний методов. Объект реализует протокол, если он содержит реализации всех методов, описанных в протоколе.


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


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


Простейшее описание протокола выглядит следующим образом:
Простейшее описание протокола выглядит следующим образом:
<source lang="objc">

@protocol ProtocolName
@protocol ProtocolName
method declarations
method declarations
@end
@end
</source>

Так протокол Serializable может быть описан следующим образом:
Так, протокол Serializable может быть описан следующим образом:
<source lang="objc">

@protocol Serializable
@protocol Serializable
- (id) initWithCoder: (NSCoder *) coder;
- (id) initWithCoder: (NSCoder *) coder;
- (void) encodeWithCoder: (NSCoder *) coder;
- (void) encodeWithCoder: (NSCoder *) coder;
@end
@end
</source>

Протокол может быть унаследован от произвольного количества других протоколов:
Протокол может быть унаследован от произвольного количества других протоколов:
<source lang="objc">

@protocol MyProto <Protocol1, Protocol2, Serializable,Drawable>
@protocol MyProto <Protocol1, Protocol2, Serializable, Drawable>
</source>

Точно также можно при описании класса задать не только родительский класс, но и набор протоколов:
Точно так же можно при описании класса задать не только родительский класс, но и набор протоколов:
<source lang="objc">

@interface MyClass : SuperClass <Protocol1, Protocol2, Serializable,Drawable>
@interface MyClass : SuperClass <Protocol1, Protocol2, Serializable, Drawable>
</source>

Для проверки во время выполнения программы поддерживается ли объектом заданный протокол объектов можно использовать сообщение conformsToProtocol::
Для проверки во время выполнения программы, поддерживается ли объектом заданный протокол объектов, можно использовать сообщение conformsToProtocol::
<source lang="objc">

if ( [myObject conformsToProtocol: @protocol (Serializable)] )
if ( [myObject conformsToProtocol: @protocol (Serializable)] )
[myObject encodeWithCoder: myCoder];
[myObject encodeWithCoder: myCoder];
</source>
Кроме того, имя протокола (протоколов) можно использовать при описании переменных для явного указания компилятору о поддержке соответствующими объектами протокола (протоколов).


Так, если переменная myObject содержит указатель на объект заранее неизвестного класса, но при этом удовлетворяющий протоколам Serializable и Drawable, то её можно описать следующим образом:
Кроме того имя протокола (протоколов) можно использовать при описании переменных для явного указания компилятору о поддержке соответствующими объектами протокола (протоколов).
<source lang="objc">

Так если переменная myObject содержит указатель на объект заранее неизвестного класса, но при этом удовлетворяющий протоколам Serializable и Drawable, то ее можно описать следующим образом:

id <Serializable,Drawable> myObject;
id <Serializable,Drawable> myObject;
</source>

Точно также, если заранее известно, что myObject будет содержать указатель на объект, унаследованный от класса Shape и поддерживающего протокол Serializable, то эту переменную можно описать следующим образом:
Точно так же, если заранее известно, что myObject будет содержать указатель на объект, унаследованный от класса Shape и поддерживающий протокол Serializable, то эту переменную можно описать следующим образом:
<source lang="objc">

Shape <Serializable> myObject;
Shape <Serializable> *myObject;
</source>

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


Как и классы, все протоколы в Objective-C представлены при помощи объектов (класса Protocol):
Как и классы, все протоколы в Objective-C представлены при помощи объектов (класса Protocol):
<source lang="objc">

Protocol * myProto = @protocol ( Serializable );
Protocol * myProto = @protocol ( Serializable );
</source>

Для предварительного объявления протоколов можно использовать следующиую конструкцию:
Для предварительного объявления протоколов можно использовать следующую конструкцию:
<source lang="objc">

@protocol MyProto, Serializable, Drawable;
@protocol MyProto, Serializable, Drawable;
</source>
Эта конструкция сообщает компилятору о том, что MyProto, Serializable и Drawable являются именами протоколов, которые будут определены позже.


== Обработка исключений ==
Это конструкция сообщает компилятору о том, что MyProto, Serializable и Drawable являются именами протоколов, которые будут определны позже.
В языке Objective-C поддерживается обработка исключений, очень похожая на используемую в языках C++ и Java.


Для этого служат директивы @try, @catch, @finally и @throw.
<source lang="objc">
Cup * cup = [[Cup alloc] init];


@try
== Обработка исключений ==
{
[cup fill];
}
@catch ( NSException * exc )
{
NSLog ( @"Exception caught:%@", exc );
}
@catch ( id exc )
{
NSLog ( @"Unknown exception caught" );
}
@finally
{
[cup release];
}
</source>
Для запуска исключения используется [[Директива (программирование)|директива]] @throw, в качестве аргумента берущая указатель на объект-исключение. Обычно в Mac OS X/NextStep для этой цели используются объекты класса NSException.
<source lang="objc">
NSException * exc = [NSException exceptionWithName: @"my-exception" reason: @"unknown-error"
userInfo: nil];
@throw exc;
</source>
Внутри @catch-блоков директива @throw может использоваться без параметра для повторного запуска обрабатываемого исключения (rethrowing exception).


== Синхронизация ==
Язык Objective-C поддерживает синхронизацию для [[Многопоточность|многопоточных]] приложений. При помощи директивы @synchronized () можно защитить фрагмент кода от одновременного выполнения сразу несколькими [[Поток выполнения|потоками]].


@synchronized () берёт на вход указатель на объект языка Objective-C (можно использовать для этой цели любой объект, в том числе и self), который играет роль [[мьютекс]]а (mutex).
В языке Objective-C поддерживается обработка исключений очень похожая на используемую в языках C++ и Java.


При попытке потока начать выполнение защищенного фрагмента проверяется, выполняется ли уже этот фрагмент каким-либо потоком. Если да, то сравниваются объекты, переданные этими потоками в @synchronized ().
Для этого служат директивы @try, @catch, @finally и @throw.


Если эти указатели совпадают, то поток, пытающийся войти в защищенный блок, будет приостановлен (suspended) до тех пор, пока первый поток не выйдет из блока. Тогда выполнение второго потока продолжится, и уже он «запрёт» этот блок для всех остальных потоков.
Cup * cup = [[Cup alloc] init];


Наличие подобной возможности заметно облегчает жизнь при написании многопоточных приложений, когда необходимо отслеживать попытки одновременного изменения одних и тех же данных сразу несколькими потоками.
@try
<source lang="objc">
- (void) criticalMethod
{
{
@synchronized ( self )
[cup fill];
{
}
// perform modifications to shared objects
@catch ( NSException * exc )
. . .
{
}
NSLog ( @"Exception caught: %@", exc );
}
@catch ( id ex )
{
NSLog ( @"Unknown exception caught" );
}
@finally
{
[cup release];
}
}
</source>
В качестве мьютекса (то есть параметра инструкции @synchronized) рекомендуется указывать объект, недоступный извне, поскольку это может привести к [[Взаимная блокировка|взаимной блокировке]], если один и тот же объект используется в качестве мьютекса двумя взаимозависимыми потоками. В частности, не рекомендуется @synchronized(self).


== Создание и уничтожение объектов ==
Для запуска исключения используется директива @throw, в качестве аргумента берущая указатель на объект-исключение. Обычно в Mac OS X/NextStep для этой цели используются объекты класса NSException.
В самом языке Objective-C нет специальных команд для создания и уничтожения объектов (подобных new и delete). Эта задача ложится на runtime-библиотеку и реализуется при помощи механизма посылки сообщений.


Реально используемой и наиболее широко распространенной схемой создания и уничтожения объектов в Objective-C является используемая в операционных системах NextStep и Mac OS X, которая и будет описана ниже.
NSException * exc = [NSException exceptionWithName: @"my-exception" reason: @"unknown-error"
userInfo: nil];
@throw exc;


Создание нового объекта разбивается на два шага — выделение памяти и инициализация объекта. Первый шаг реализуется методом класса alloc (реализованном в классе NSObject), который выделяет необходимое количество памяти (данный метод используется для выделения памяти не только для объектов класса NSObject, но и любого унаследованного от него класса). При этом в атрибут isa записывается указатель на class object соответствующего класса.


Обратите внимание, что сообщение alloc посылается class object-у требуемого класса и это сообщение возвращает указатель на выделенную под объект память.
Внутри @catch-блоков директива @throw может использоваться без параметра для повторного запуска обрабатываемого исключение (rethrowing exception).


Собственно сама инициализация объекта (то есть установка значений его instance-переменных, выделение дополнительных ресурсов и т. п.) осуществляется другими методами, по традиции имена этих методов начинаются с init. Обычно такое сообщение посылается сразу же после сообщения alloc по адресу, возвращенному этим сообщением.
<source lang="objc">
id anObject = [[Rectangle alloc] init];
</source>
Приведённая выше конструкция является правильным способом создания объекта. Обратите внимание, что следующая конструкция может в ряде случаев не работать:
<source lang="objc">
id anObject = [Rectangle alloc];


[anObject init];
== Синхронизация ==
</source>
Это связано с тем, что для ряда классов метод init может вернуть совсем другой указатель (а не self).


Простейшими примерами того, когда может возникать подобная ситуация, являются [[Singleton|синглтоны]] (тогда, если один экземпляр класса уже существует, то метод init освободит выделенную alloc’ом память и вернет указатель на уже созданный единственный экземпляр) и кэширование объектов, когда для увеличения производительности выделение объектов происходит сразу блоками и объекты не уничтожаются, а сохраняются для переиспользования.


При создании нового класса обычно нет необходимости переопределять метод alloc, а вот необходимость переопределения метода init возникает достаточно часто.
Язык Objective-C поддерживает синхронизацию для многопоточных приложений. При помощи директивы @synchronized () можно защитить фрагмент кода от одновременного выполнения сразу несколькими нитями.


Обратите внимание, что метод(ы) init является обычным методом, ничем не выделяющимся среди остальных (в отличие от C++, где конструктор — это особый метод, у которого, например, нельзя взять адрес).
@synchronized () берет на вход указатель на объект языка Objective-C (можно использовать для этой цели любой объект, в том числе и self), который играет роль мьютекса(mutex).


Поэтому при создании нового класса и метода init вызов переопределенного метода init (при помощи [super init]) должен быть произведен явно в самом начале метода.
При попытке нити начать выполнение защищенного фрагмента проверяется не выполняется ли уже этот фрагмент какой-либо нитью. Если да, то сравниваются объекты, переданные этими нитями в @synchronized ().


Довольно часто у объектов бывает сразу несколько методов, начинающихся с init, например init, initWithName:, initWithContentsOfFile: и т. д.
Если эти указатели различются, то нить, пытающаяся войти в защищеный блок будет приостановлена (suspended) до тех пор, пока первая нить не выйдет из блока. Тогда выполение второй нити продолжится и уже она "запрет" этот блок для всех остальных нитей.


Установившейся практикой в таком случае является выделение среди всех init-методов одного, называемого designated initializer. Все остальные init-методы должны вызывать его и только он вызывает унаследованный init метод.
Наличие подобной возможности заметно облегчает жизнь при написании многонитевых приложений, когда необходимо отслеживать попытки одновременного изменения одних и тех же данных сразу несколькими нитями.
<source lang="objc">

- (id) initWithName: (const char *) theName // designated initializer
- (void) criticalMethod
{
{
self = [super init]; // call inherited method
@synchronized ( self )
if (self)
{
{
// perfrom modifications to shared objects
name = strdup ( theName );
. . .
}
}
return self;
}
}


- (id) init
{
return [self initWithName: ""];
}
</source>
В ряде случаев оказывается удобным совместить выделение памяти и инициализацию объекта в один метод (класса), например в классе NSString есть ряд методов класса, возвращающих уже готовый (проинициализированный) объект:
<source lang="objc">
+ (id) stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc
+ (id) stringWithFormat:(NSString *)format, ...
</source>
Mac OS X (как и NextStep) для управления временем жизни объектов используют reference counting — каждый объект содержит внутри себя некоторый счетчик, при создании устанавливаемый в единицу.


Посылка объекту сообщения retain увеличивает значение этого счетчика на единицу (так, все контейнерные классы библиотеки Foundation при помещении в них объекта посылают ему сообщение retain).
== Создание и уничтожение объектов ==


Установившейся практикой является посылка объекту сообщения retain всеми заинтересованными в нём сторонами (объектами), то есть, если вы запоминаете ссылку на объект, то следует послать ему сообщение retain.


Когда объект перестает быть нужен, ему просто посылается сообщение release.
В самом языке Objective-C нет специальных команд для создания и уничтожения объектов (подобных new и delete). Эта задача ложится на runtime-библиотеку и реализуется при помощи механизма посылки сообщений.


Данное сообщение уменьшает значение счетчика на единицу и, если это значение стало меньше единицы, уничтожает данный объект.
Реально используемой и наиболее широко распространенной схемой создания и уничтожения объектов в Objective-C является используемая в операционных системах NextStep и Mac OS X, которая и будет описана ниже.


Перед уничтожением объекта ему посылается сообщение dealloc, позволяющее объекту произвести свою деинициализацию. При этом это также является обычным сообщением и в нём вы явно должны в конце вызвать унаследованную реализацию через [super dealloc].
Создание нового объекта разбивается на два шага - выделение памяти и инициализация объекта. Первый шаг реализуется методом класса alloc (реализованном в классе NSObject), который выделяет необходимое количество памяти (данный метод используется для выделения памяти не только для объектов класса NSObject, но и любого унаследованного от него класса). При этом выделяемая память обнуляется и в атрибут isa записывается указатель на class object соответствующего класса.
<source lang="objc">
- (void) dealloc
{
. . .
[super dealloc];
}
</source>


== Управление памятью ==
Обратите внимание, что сообщение alloc посылается class object-у требуемого класса и это сообщение возврщает указатель на выделенную под объект память.


=== Базовые принципы ===
Собственно сама инициализация объекта (т.е. установка значений его instance-переменных, выделение дополнительных ресурсов и т.п.) осуществляется другими методами, по традиции имена этих методов начинаются с init. Обычно такое сообщение посылается сразу же после сообщение alloc, по адресу, возвращенному этим сообщением.
Управление памятью в Objective-C базируется на принципе «владения объектом». Основные правила управления памятью в Objective-C можно записать так:
* Для получения объекта во владение необходимо вызвать метод, содержащий в названии «alloc», «new» либо «copy». Например, alloc, newObject, mutableCopy.
* Для освобождения объекта, который был получен при помощи перечисленных выше функций, необходимо вызвать функцию «release» либо «autorelease». Во всех остальных случаях освобождение объекта не требуется.
* Если полученный объект должен быть сохранен, необходимо либо стать его владельцем (вызвав retain), либо создать его копию (вызов, содержащий в названии «copy»).
Данные правила базируются на соглашении по именованию в Objective-C и, в то же время, сами являются основой этого соглашения.


=== Базовые принципы на практике ===
id anObject = [[Rectangle alloc] init];
Предположим, в программе существует класс Company, у которого есть метод workers.
<source lang="objc">
@interface Company : NSObject
{
NSArray *workers;
}
-(NSArray*)workers;
@end
</source>
Рассмотрим небольшой пример использования такого класса:
<source lang="objc">
Company *company = [[Company alloc] init];
// ...
NSArray *workers = [company workers];
// ...
[company release];
</source>
Так как объект класса Company создается явно, он должен быть удален по окончании использования ([company release]). В то же время, название метода workers не говорит о том, кто должен удалять массив. В такой ситуации считается, что списком работников управляет объект Компания и его удалять не требуется.


;Convenience конструкторы
Приведенная выше конструкцуия является правильным способом создания объекта. Обратите внимание, что следующая конструкция может в ряде случаев не работать:


Многие классы позволяют совместить создание объекта с его инициализацией при помощи методов, называемых convenience конструкторы; такие методы обычно называются +className… Можно предположить, что вызывающая сторона ответственна за управление временем жизни объекта, но подобное поведение противоречило бы соглашению по именованию в Objective-C.
id anObject = [Rectangle alloc];
<source lang="objc">
Company *company = [Company company];
[company release];
</source>
В приведенном коде вызов [company release] '''недопустим''', так как в данном случае управление временем жизни объекта '''должно''' осуществляться при помощи autorelease-пула.


Ниже приводится пример корректной реализации метода company:
[anObject init];
<source lang="objc">
+(Company*)company
{
id ret = [[Company alloc] init];
return [ret autorelease];
}
</source>


;autorelease
Это связано с тем, что для ряда классов метод init может вернуть совсем другой указатель (а не self).


Вернемся к методу workers класса Company. Так как возвращается массив, временем жизни которого вызывающая сторона не управляет, реализация метода workers будет выглядеть приблизительно так:
Простейшими примерами того, когда может возникать подобная ситуация, являются синглетоны (тогда, если один экземпляр класса уже существует, то метод init освободит выделенную alloc'ом память и вернет указатель на уже созданный единственный экземпляр) и кэширование объектов, когда для увеличения производительности, выделение объектов происходит сразу блоками и объекты не уничтожаются, а сохраняются для переиспользования.
<source lang="objc">
-(NSArray*)workers
{
NSArray *copy = [[NSArray alloc] initWithArray:workers];
return [copy autorelease];
}
</source>
Вызов autorelease добавляет объект copy в autorelease-пул, вследствие чего возвращаемый объект получит сообщение release при удалении пула, в который он был добавлен. Если объекту, добавленному в autorelease-пул, послать сообщение release самостоятельно, при удалении autorelease-пула возникнет ошибка.


;Возвращение объекта по ссылке
При создании нового класса обычно нет необходимости переопределять метод alloc, а вот необходимость переопределения метода init возникает достаточно часто (хотя во многих случаях можно положится на обнуление памяти alloc'ом).


В ряде случаев объекты возвращаются по ссылке, например, метод класса NSData initWithContentsOfURL:options: error: в качестве параметра error принимает (NSError **)errorPtr. В этом случае так же работает соглашение по именованию, из которого следует, что явного запроса на владение объектом нет, соответственно, удалять его не требуется.
Обратите внимание, что метод(ы) init является обычным методом, ничем не выделяющимся среди остальных (в отличии от С++, где конструктор - это особый метод, у которого например нельзя взять адрес).


;Удаление объектов
Поэтому при создании нового класса и метода init вызов переопределенного метода init (при помощи [super init]) должен быть произведен явно в самом начале метода.


Когда счетчик ссылок объекта становится равным нулю, объект удаляется. При этом у объекта вызывается метод -(void)dealloc. Если в объекте содержатся какие-то данные, их необходимо удалить в этой функции.
Довольно часто у объектов бывает сразу несколько методов, начинающихся с init, например init, initWithName:, initWIthContentsOfFile: и т.д.
<source lang="objc">
-(void)dealloc
{
[workers release];
[super dealloc];
}
</source>
После того, как всем переменным класса было послано сообщение release, необходимо вызвать метод dealloc базового класса. Это единственный случай, в котором допустим вызов метода dealloc напрямую.


''Не существует никаких гарантий относительно времени вызова метода dealloc. В ряде случаев он вообще может не вызываться при завершении работы приложения для экономии времени, так как по завершении приложения ОС в любом случае освободит выделенную память. Соответственно, в методе dealloc не должно располагаться никаких методов, отвечающих за закрытие сокетов, файлов и т. п.''
Установившейся практикой в таком случае является выделение среди всех init-методов одного, нзываемого designated initializer. Все остальные init-методы должны вызывать его и только он вызывает унаследованный init метод.


=== Autorelease-пул ===
- initWithName: (const char *) theName // designated initializer
Autorelease-пул используется для хранения объектов, которым будет послано сообщение release при удалении пула. Для того, чтобы добавить объект в autorelease-пул, ему необходимо отправить сообщение autorelease.
{
[super init]; // call inherited method


В приложениях Cocoa autorelease-пул всегда доступен по умолчанию. Для не-AppKit приложений необходимо создавать и управлять временем жизни autorelease-пула самостоятельно.
name = strdup ( theName );
}


Autorelease-пул реализуется классом NSAutoreleasePool.
- init
<source lang="objc">
int main (int argc, const char * argv[])
{
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
return [self initWithName: ""];

Company *company = [Company company];
NSArray *workers = [company workers];

[pool drain];
return 0;
}
}
</source>
Удалить объекты из autorelease-пула можно не только посредством отправки пулу сообщения release, но и с помощью сообщения drain. Поведение release и drain в среде с подсчетом ссылок идентично. Но в случае работы в GC среде drain вызывает функцию objc_collect_if_needed.


;Autorelease-пул в многопоточной среде


В Cocoa для каждого из потоков создается свой собственный autorelease-пул. По завершении потока autorelease-пул уничтожается и всем содержащимся в нём объектам посылается сообщение release.
В ряде случаев оказывается удобным совместить выделение памяти и инициализацию объекта в один метод (класса), например в классе NSString есть ряд методов класса, возвращающих уже готовый (проинициализированный) объект:


Autorelease-пул главного потока периодически пересоздается с целью уменьшения используемой памяти приложением. Во всех остальных потоках заниматься пересозданием autorelease-пула необходимо самостоятельно, что крайне актуально для долгоживущих потоков.
+ (NSString *) initStringWithCString: (const char *) str;
+ (NSString *) initStringWithFormat: (NSString *) format, ...;


=== Копирование объектов ===
Mac OS X (как и NextStep) для управления времением жизни объектов используют reference counting - каждый объект содержит внутри себя некоторый счетчик, при создании устанавливаемый в единицу.
Все объекты в Objective-C потенциально поддерживают копирование. Для того, чтобы создать копию объекта, необходимо вызвать метод copy, определённый в классе NSObject. Для создания копии будет вызван метод copyWithZone протокола NSCopying. NSObject не имеет поддержки этого протокола и при необходимости протокол NSCopying должен быть реализован в классах-наследниках.


Копии бывают двух видов: поверхностная копия (shallow copy) и полная копия (deep copy). Разница между этими копиями состоит в том, что при создании поверхностной копии копируются не данные, а ссылка на объект с данными. В случае полной копии копируется объект с данными.
Посылка объекту сообщения retain увеличивает значение этого счетчика на единицу (так все контейнерные классы библиотеки Foundation при помещении в них объекта, посылают ему сообщение retain).


;Пример реализации
Установившейся практикой является посылка объекту сообщения retain всеми, заинтересованными в нем сторонами (объектами), т.е. если вы запоминаете ссылку на объект, то следует послать ему сообщение retain.


Реализация копирования может различаться в зависимости от того, поддерживает ли класс-родитель протокол NSCopying. Пример кода для ситуации, когда родитель не реализует протокол NSCopying:
Когда объект перестает быть нужен, то ему просто посылается сообщение release.
<source lang="objc">
@interface Company : NSObject <NSCopying>
{
NSString *name;
}
@property(retain) NSString *name;
-(id)copyWithZone:(NSZone *)zone;
@end


@implementation Company
Данное сообщение уменьшает значение счетчика на единицу и, если это значение стало меньше единицы, уничтожает данный объект.
@synthesize name;
-(id)copyWithZone:(NSZone *)zone
{
id copy = [[[self class] allocWithZone:zone] init];
[copy setName:[self name]];
return copy;
}
@end
</source>
Если родитель поддерживает протокол NSCopying, реализация будет несколько иной: вызов allocWithZone заменяется на copyWithZone.
<source lang="objc">
id copy = [super copyWithZone:zone];
</source>


;Копирование неизменяемых объектов
Перед уничтожением объекта ему посылается сообщение dealloc, позволяющее объекту произвести свою деинициализацию. При этом это также является обычным сообщением и в нем Вы явно должны в конце вызвать унаследованную реализацию через [super dealloc].


Для immutable объектов создание копии нецелесообразно, и можно ограничиться отправкой самому себе сообщения retain.
- (void) dealloc
<source lang="objc">
-(id)copyWithZone:(NSZone *)zone
{
{
. . .
return [self retain];
[super dealloc];
}
}
</source>



== Категории ==
== Категории ==
Язык Objective-C обладает возможностью добавлять новые методы к уже существующим классам. Аналогичной возможностью обладают языки [[Ruby]], [[C Sharp|C#]], [[JavaScript]] и другие. При этом не требуется исходников класса и добавленные методы автоматически становятся доступными всем классам, унаследованным от изменяемого. Так можно добавить новый метод классу NSObject и этот метод автоматически добавится во все остальные классы.


Механизм, позволяющий расширять уже существующие классы (путём добавления новых методов, новые instance-переменные добавить таким образом нельзя), называется категорией.


Категория имеет своё имя, список методов и имя класса, который она расширяет. Описание категории имеет следующий вид:
Язык Objective-C обладет крайне редко встречающейся возможностью добавлять новые методы к уже существующим классам. При этом не требуется исходников класса и добавленные методы автоматически становятся доступными всем классам, унаследованным от изменяемого.
<source lang="objc">

Так можно добавить новый метод классу NSObject и этот метод автоматические добавится во все остальные классы !

Аналогичной возможностью обладает язык Ruby, но Objective-C является компилируемым языком, а Ruby - интерпретируемым.

Механизм, позволяющий расширять уже существующие классы (путем добавление новых методов, новые instance-переменные добавить таким образом нельзя), называется категориями.

Категория имеет свое имя, список методов и имя класса, который она расширяет. Описание категории имеет следующий вид:

#import "ClassName.h"
#import "ClassName.h"
@interface ClassName ( CategoryName )
@interface ClassName ( CategoryName )
methods declarations
methods declarations
@end
@end
</source>

Реализация категории выглядить следующим образом:
Реализация категории выглядит следующим образом:
<source lang="objc">

#import "CategoryName.h"
#import "CategoryName.h"
@interface ClassName ( CategoryName )
@implementation ClassName ( CategoryName )
methods bodies
methods bodies
@end
</source>
С помощью категорий можно создавать свойства (property), которые будут доступны только для чтения другим классам и readwrite внутри своего класса:
<source lang="objc">
@interface ClassName
{
BOOL flag;
}
@property (assign, readonly) BOOL flag;
@end
@end


#import "ClassName"
Class objects и Objective-C runtime
@implementation ClassName () // Пустая категория
@property (assign, readwrite) BOOL flag;
@end


@implementation ClassName
При компиляции программы на Objective-C компилятор для каждого введенного класса автоматически создает так называемый class object - полноценный объект, содержащий в себе всю информацию о данном классе, включая название, суперкласс, список методов и instance-переменных.
@synthesize flag;


-(void) someAction
При этом такой объект является полноценным объектом, т. е. ему можно посылать сообщения, передавать в качестве параметра.
{
self.flag = YES;
}
@end
</source>
Кроме всего прочего категории можно использовать для того, чтобы обеспечить реализацию классом какого-либо нового протокола, например:
<source lang="objc">
@protocol Printable // сущности, которые можно распечатать
-(void) print;
@end


@interface NSString(Printable) <Printable> // добавляем системному классу NSString возможность быть распечатанным
Одной из особенностью class object'а является то, что он поддерживает все методы класса NSObject, т.е. когда ему посылается сообщение, то сначале идет поиск среди методов класса, а если метод не найден, то поиск продолжается среди instance-методов класса NSObject.
@end


@implementation NSString(Printable) // реализуем новую функциональность
Еще одной интересной особенностью является возможность инциализации class object'ов - в начале работы приложения каждому class object'у посылается сообщение (класса) initialize.
-(void) print
{
NSLog(@"Меня распечатали %@!", self);
}
@end
</source>
Это избавляет от необходимости писать класс-адаптер PrintableString для NSString.


== Class objects и Objective-C runtime ==
Это сообщение гарантированно посылается каждому class object'у, причем всего один раз и до того, как ему будет послано любое другое сообщение. Простейшим примером примененния такого сообщения является реализация Singleton'ов - именно в методе initialize следует создать тот самый единственный экземпляр объекта и запомнить его в static-переменной.
При компиляции программы на языке Objective-C компилятор для каждого введённого класса автоматически создаёт так называемый class object — полноценный объект, содержащий в себе всю информацию о данном классе, включая название, суперкласс, список методов и instance-переменных.


При этом такой объект является полноценным объектом, то есть ему можно посылать сообщения, передавать в качестве параметра.
Если посмотреть на Objective-C runtime от Apple, то мы найдем большое количество С-функция, служащих для работы с классами (непосредственно во время выполения программы).


Одной из особенностей class object’а является поддержка всех методов класса NSObject. То есть при отправке сообщения поиск по селектору сначала ведётся среди методов класса, и если метод не найден, поиск продолжается среди instance-методов класса NSObject.
Наиболее интересными являются следующие:


Ещё одной особенностью является возможность инициализации class object’ов — в начале работы приложения каждому class object’у посылается сообщение (класса) initialize.
<nowiki>Method class_getInstanceMethod( Class aClass, SEL aSelector );

Это сообщение гарантированно посылается каждому class object’у, причём всего один раз и до того, как ему будет послано любое другое сообщение. Простейшим примером применения такого сообщения является реализация Singleton’ов — именно в методе initialize следует создать тот самый единственный экземпляр объекта и запомнить его в static-переменной.

Objective-C runtime от Apple содержит большое количество С-функций, служащих для работы с классами (непосредственно во время выполнения программы).

Наиболее интересными являются следующие:
<source lang="objc">
Method class_getInstanceMethod( Class aClass, SEL aSelector );
Method class_getClassMethod ( Class aClass, SEL aSelector );
Method class_getClassMethod ( Class aClass, SEL aSelector );
struct objc_method_list * class_nextMethodList(Class theClass, void ** iterator);
struct objc_method_list * class_nextMethodList(Class theClass, void ** iterator);
Строка 583: Строка 792:
char ** type, int * offset );
char ** type, int * offset );
Ivar class_getInstanceVariable ( Class aClass, const char * aVariableName );
Ivar class_getInstanceVariable ( Class aClass, const char * aVariableName );
</nowiki>
</source>
Функция class_getInstanceMethod возвращает указатель на структуру (objc_method), описывающую заданный instance-метод данного класса.


Функция class_getInstanceMethod возвращает указатель на структуру (objc_method) описывающую заданный instance-метод данного класса.
Функция class_getClassMethod возвращает указатель на структуру (objc_method), описывающую заданный метод данного класса.

Функция class_getClassMethod возвращает указатель на структуру (objc_method) описывающую заданный метод данного класса.

Функция class_nextMethodList возвращет один из списков методов для заданного класса. Приводимый ниже фрагмент кода позволяет перебрать все методы для данного класса.


Функция class_nextMethodList возвращает один из списков методов для заданного класса. Приводимый ниже фрагмент кода позволяет перебрать все методы для данного класса.
<source lang="objc">
void * iterator = 0;
void * iterator = 0;
struct objc_method_list * mlist;
struct objc_method_list * methodList;


//
//
Строка 600: Строка 808:
methodList = class_nextMethodList( classObject, &iterator )
methodList = class_nextMethodList( classObject, &iterator )


while ( methodList != NULL )
while ( methodList != nil )
{
{
// …do something with the method list here…
// …do something with the method list here…
Строка 606: Строка 814:
methodList = class_nextMethodList ( classObject, &iterator );
methodList = class_nextMethodList ( classObject, &iterator );
}
}
</source>

Функция class_addMethods позволяет добавлять новые методы к заданному классу.
Функция class_addMethods позволяет добавлять новые методы к заданному классу.


Строка 613: Строка 821:
Функция method_getNumberOfArguments Возвращает количество аргументов для заданного метода.
Функция method_getNumberOfArguments Возвращает количество аргументов для заданного метода.


Функция method_getSizeOfArguments возвращает размер места на стеке, занимаемого всему аргументами данного метода.
Функция method_getSizeOfArguments возвращает размер места на стеке, занимаемого всеми аргументами данного метода.


Функция method_getArgumentInfo возвращает информацию об одном из аргументов для заданного метода..
Функция method_getArgumentInfo возвращает информацию об одном из аргументов для заданного метода.


Функция class_getInstanceVariable возвращает информацию об instance-переменной класса в виде указателя на структуру objc_ivar.
Функция class_getInstanceVariable возвращает информацию об instance-переменной класса в виде указателя на структуру objc_ivar.


Для кодирования информации о типах используется специальное строковое представление, однозначно сопоставляющее каждому типу данных некоторую строку. Явно получить такую строку для проивзольного типа можно при помощи конструкции @encode ().
Для кодирования информации о типах используется специальное строковое представление, однозначно сопоставляющее каждому типу данных некоторую строку. Явно получить такую строку для произвольного типа можно при помощи конструкции @encode ().
<source lang="objc">

char * buf1 = @encode ( int ** );
char * buf1 = @encode ( int ** );
char * buf2 = @encode ( struct key );
char * buf2 = @encode ( struct key );
char * buf3 = @encode ( Rectangle );
char * buf3 = @encode ( Rectangle );
</source>



== Разное ==
== Разное ==
Официальный сайт [[Apple]]<ref>{{Cite web |url=https://developer.apple.com/ |title=Apple developer |access-date=2017-09-29 |archive-date=2011-02-25 |archive-url=https://web.archive.org/web/20110225153844/http://developer.apple.com/ |deadlink=no }}</ref> — главный источник информации о языке. Форум разработчиков, примеры кода и полная версия документации доступны только зарегистрированным разработчикам.


[[IDE]] [[Xcode]] — основное средство разработки на языке Objective-C. IDE поддерживает только [[Операционная система|ОС]] [[Mac OS X]] и распространяется бесплатно через магазин приложений [[Apple App Store]].


Полезную информации по языку Objective-C можно найти в news-группе<ref>{{Cite web |url=http://groups.google.com/group/comp.lang.objective-c |title=comp.lang.objective-c |access-date=2007-06-14 |archive-date=2006-11-15 |archive-url=https://web.archive.org/web/20061115222215/http://groups.google.com/group/comp.lang.objective-c |deadlink=no }}</ref> и архивах списка рассылки<ref>{{Cite web |url=http://lists.apple.com/mailman/listinfo |title=Objc-language |access-date=2007-12-17 |archive-date=2007-12-18 |archive-url=https://web.archive.org/web/20071218104056/http://lists.apple.com/mailman/listinfo/ |deadlink=yes }}</ref>.
Крайне полезным (и пожалуй главным) источником информации по языку является сайт developer.apple.com.


Проект [[GNUstep]]<ref>{{Cite web |url=http://www.gnustep.org/ |title=Официальный сайт проекта GNUstep |access-date=2022-05-15 |archive-date=2021-01-26 |archive-url=https://web.archive.org/web/20210126033826/http://www.gnustep.org/ |deadlink=no }}</ref> — попытка создания аналогов закрытых библиотек Foundation и AppKit, используемых в [[NextStep]] и [[Mac OS X]]. Исходный код библиотек написан на языке Objective-C и распространяется свободно. На сайте проекта доступны примеры использования языка и исходный код нескольких приложений.
Также ряд полезной информации по языку Objective-C можно найти в news-группе comp.lang.objective-c.


Objective-C доступен практически в каждом дистрибутиве [[GNU/Linux]] благодаря компилятору gobjc, созданному проектом [[gcc]].
Проект GNUstep представляет собой попытку создания open-source библиотек на Objective-C, являющихся аналогом библиотек Foundation и AppKit в NextStep и Mac OS X.


Для работы с Objective-C под [[Операционная система|ОС]] [[Microsoft Windows|Windows]] используют эмуляторы среды [[POSIX]] (бесплатные):
На сайте проекта GNUstep Вы найдете много примеров использование языка Objective-C и приложений, написанных на нем.
* [[mingw]];
* [[cygwin]];
* [[сервисы Microsoft Windows для UNIX]].


== См. также ==
Поскольку компилятор gcc поддерживает язык Objective-C, то практически каждый дистрибутив Linux позволяет установить поддержку для него.
* [[Си (язык программирования)|Си]]
* [[C++]]
* [[Swift (язык программирования)|Swift]]


== Примечания ==
Для работы с Objective-C под MS Windows можно использовать компиляторы mingw и cygwin, необходимо только установить библиотеку для поддержки языка.
{{примечания}}


== Литература ==
==Ссылки==
* {{книга
* [http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/ Introduction to The Objective-C Programming Language (Apple Developer Connection)] [http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/ObjC.pdf (скачать PDF)]
|заглавие = Программирование для iOS 7. Основы Objective-C, Xcode и Cocoa
|оригинал = iOS 7 Programming Fundamentals: Objective-C, Cocoa, and Xcode Basics
|автор = Мэтт Нойбург
|страниц = 384
|isbn = 978-5-8459-1895-6
|год = 2014
|место = М.
|издательство = [[Вильямс (издательство)|«Вильямс»]]
}}
* {{книга
|заглавие = Objective-C и программирование для Mac OS X и iOS, 2-е издание
|оригинал = Learn Objective-C on the Mac: For OS X and iOS, 2nd edition
|автор = Скотт Кнастер, Вакар Малик, Марк Далримпл
|страниц = 416
|isbn = 978-5-8459-1826-0
|год = 2013
|место = М.
|издательство = [[Вильямс (издательство)|«Вильямс»]]
}}
* {{книга
|заглавие = Разработка приложений для Mac OS X Lion. Программирование на Objective-C в Xcode
|оригинал = Beginning Mac OS X Lion Apps Development
|автор = Майкл Приват, Роберт Уорнер.
|страниц = 384
|isbn = 978-5-8459-1789-8
|год = 2012
|место = М.
|издательство = [[Вильямс (издательство)|Вильямс]]
}}


== Ссылки ==
{{compu-lang-stub}}
; Ресурсы
* [http://classroomm.com/objective-c/ Форум Стивена Кохана, посвященный Objective-C 2.0]{{ref-en}}
* [http://code.google.com/p/nobjective/ Мост для работы с Objective-C из среды выполнения .NET (Macintosh, Mono)]

; Статьи
* [https://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/ Introduction to The Objective-C Programming Language (Apple Developer Connection)] [https://web.archive.org/web/20030704024148/http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/ObjC.pdf (PDF)]{{ref-en}}
* [http://www.insk.org/objectivecprimer/ Изучаем Objective-C: Букварь]{{ref-ru}}
* [http://steps3d.narod.ru/tutorials/objective-c-tutorial.html Язык программирования Objective-C]{{ref-ru}}
* [http://www.informit.com/articles/article.aspx?p=1353398 Advanced Flow Control for Objective-C]{{ref-en}}
* [http://www.informit.com/articles/article.aspx?p=1331446 Debugging C-Family Languages]{{ref-en}}
* [http://www.informit.com/articles/article.aspx?p=1353397 Fun with the Objective-C Runtime]{{ref-en}}
* [http://www.informit.com/articles/article.aspx?p=1272496 Objective-C for C++ Programmers]{{ref-en}} и [http://netsago.org/ru/docs/1/15/ перевод]{{ref-ru}}
* [http://www.informit.com/articles/article.aspx?p=1353401 Steve Kochan on the Evolution of Objective-C]{{ref-en}}
* [http://www.informit.com/articles/article.aspx?p=1322281 The Dynamic Languages Renaissance]{{ref-en}}


{{Языки программирования}}
{{Языки программирования}}
[[Категория:Языки программирования]]
{{Язык программирования Си}}
[[Категория:Объектно-ориентированные языки программирования]]


[[Категория:Языки программирования семейства Си]]
[[bg:Objective-C]]
[[Категория:Объектно-ориентированные языки программирования]]
[[cs:Objective-C]]
[[Категория:Языки с динамической типизацией]]
[[da:Objective-C]]
[[Категория:Свободные компиляторы и интерпретаторы]]
[[de:Objective-C]]
[[Категория:Программное обеспечение Apple]]
[[en:Objective-C]]
[[Категория:NeXT]]
[[eo:Objective-C]]
[[Категория:GNUstep]]
[[es:Objective-C]]
[[fi:Objective-C]]
[[fr:Objective C]]
[[hu:Objective-C programozási nyelv]]
[[it:Objective C]]
[[ja:Objective-C]]
[[ko:오브젝티브-C]]
[[nl:Objective-C]]
[[no:Objective-C]]
[[pl:Objective-C]]
[[sk:Objective-C]]
[[sv:Objective-C]]
[[th:ภาษาอ็อบเจกต์ทีฟซี]]
[[tr:Objective-C]]
[[zh:Objective-C]]

Текущая версия от 14:57, 2 ноября 2024

Objective-C
Класс языка объектно-ориентированный, мультипарадигмальный: рефлексивно-ориентированный
Появился в 1983
Автор Брэд Кокс
Расширение файлов .h, .m, .mm или .C
Выпуск
Система типов слабая, статическая / динамическая
Основные реализации Cocoa, Cocoa Touch, gcc, LLVM + Clang
Испытал влияние Smalltalk, C
Повлиял на Java, Objective-J, Swift
Сайт developer.apple.com/libr…
Логотип Викисклада Медиафайлы на Викискладе

Objective-C — компилируемый объектно-ориентированный язык программирования, используемый корпорацией Apple, построенный на основе языка Си и парадигм Smalltalk. В частности, объектная модель построена в стиле Smalltalk — то есть объектам посылаются сообщения.

Язык Objective-C является надмножеством языка Си, поэтому Си-код полностью понятен компилятору Objective-C.

Компилятор Objective-C входит в GCC и доступен на большинстве основных платформ. Язык используется в первую очередь для macOS (Cocoa) и GNUstep — реализаций объектно-ориентированного интерфейса OpenStep. Также язык используется для iOS (Cocoa Touch).

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

Многие программисты увидели в объектно-ориентированном программировании потенциальное решение возникшей проблемы. С одной стороны, Smalltalk использовали почти все более-менее сложные системы. С другой — использование виртуальных машин повышало требования к ресурсам.

Objective-C был создан Брэдом Коксом в начале 1980-х в его компании Stepstone. Он пытался решить проблему повторного использования кода.

Целью Кокса было создание языка, поддерживающего концепцию software IC, подразумевающей возможность собирать программы из готовых компонентов (объектов), подобно тому как сложные электронные устройства могут быть собраны из набора готовых интегральных микросхем.

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

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

Objective-C является расширением С: любая программа на С является программой на Objective-C.

Одной из отличительных черт Objective-C является динамичность: решения, обычно принимаемые на этапе компиляции, здесь откладываются до этапа выполнения.

Objective-C — message-oriented-язык, в то время как C++ — function-oriented: в Objective-C вызовы метода интерпретируются не как вызов функции (хотя к этому обычно все сводится), а как посылка сообщения (с именем и аргументами) объекту, подобно тому, как это происходит в Smalltalk.

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

Привязка сообщения к соответствующей функции происходит на этапе выполнения.

Язык Objective-C поддерживает работу с метаинформацией — так, на этапе выполнения можно узнать класс объекта, список его методов (с типами передаваемых аргументов) и instance-переменных, проверить, является ли класс потомком заданного и поддерживает ли он заданный протокол и т. п.

В языке есть поддержка протоколов (понятия интерфейса объекта и протокола четко разделены). Поддерживается наследование (не множественное); для протоколов поддерживается множественное наследование. Объект может быть унаследован от другого объекта и сразу нескольких протоколов (хотя это скорее не наследование протокола, а его поддержка).

На данный момент язык Objective-C поддерживается компиляторами Clang и GCC (под управлением Windows используется в составе MinGW или cygwin).

Некоторые функции языка перенесены в runtime-библиотеку и сильно зависят от неё. Вместе с компилятором gcc поставляется минимальный вариант такой библиотеки. Также можно свободно скачать runtime-библиотеку компании Apple: Apple’s Objective-C runtime.

Эти две runtime-библиотеки похожи (основные отличия в именах методов). Далее примеры будут ориентироваться на runtime-библиотеку Apple.

Синтаксис языка

[править | править код]

В языке Objective-C для обозначения объектов используется специальный тип id (это аналог типа Object в Java). Переменная типа id фактически является указателем на произвольный объект. Для обозначения нулевого указателя на объект используется константа nil (= NULL).

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

Тем самым язык поддерживает проверку типов, но в нестрогой форме (то есть найденные несоответствия возвращаются как предупреждения, а не ошибки).

Для посылки сообщений используется следующий синтаксис:

   [receiver message];

В этой конструкции receiver является указателем на объект, а message — именем метода.

В отличие от языка C++, посылка сообщения 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) является создание экземпляров данного класса (паттерн Factory method)[2].

При этом само имя класса играет двойную роль — с одной стороны оно выступает как тип данных (то есть он может быть использован для описания указателей на объекты данного класса). А с другой стороны имя класса может выступать в качестве объекта, которому посылается сообщение (в сообщениях имя класса может принимать участие только как 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. Сейчас они входят в библиотеку Foundation, на которой строятся приложения для OS X и iOS.

С одним из них — NSString — мы столкнемся в данной статье. Этот класс служит для работы со строками (при этом в качестве внутреннего представления символов используется Юникод).

Компилятор поддерживает данный тип, автоматически переводя конструкции вида @"my string" в указатель на объект класса NSString, содержащий данную строку (точнее его подкласса, соответствующего константным строкам).

Свойства

Допустим, в классе Company существует instance-переменная name.

@interface Company : NSObject
{
     NSString *name;
}

Для доступа к ней извне лучше всего воспользоваться свойствами, которые появились в Objective-C 2.0. Для объявления свойств используется ключевое слово @property.

@property (retain) NSString *name;

В скобках перечисляются атрибуты доступа к instance-переменной. Атрибуты разделяются на 3 основные группы.

Имена акцессора и мутатора

  • getter=getterName, используется для задания имени функции, используемой для извлечения значения instance-переменной.
  • setter=setterName, используется для задания имени функции, используемой для установки значения instance-переменной.

Ограничение чтения/записи

  • readwrite — у свойства есть как акцессор, так и мутатор. Является атрибутом по умолчанию.
  • readonly — у свойства есть только акцессор.

Эти атрибуты взаимоисключают друг друга. И последняя группа атрибуты мутатора.

  • assign — для задания нового значения используется оператор присваивания. Используется только для POD-типов либо для объектов, которыми мы не владеем.
  • retain — указывает на то, что для объекта, используемого в качестве нового значения instance-переменной, управление памятью происходит вручную (не забываем потом освободить память).
  • copy — указывает на то, что для присваивания будет использована копия переданного объекта.
  • weak — аналог assign при применении режима автоматического подсчёта ссылок. (ARC должен поддерживаться компилятором)
  • strong — аналог retain при применении режима автоматического подсчёта ссылок. (ARC должен поддерживаться компилятором)

При работе под GC никакой разницы между использованием assign, retain, copy нет. Для создания кода свойств, в соответствии с тем, как они описаны в объявлении, можно воспользоваться автогенерацией кода:

@synthesize name;

Автоматически созданный код — не всегда подходящее решение и может потребоваться создание методов доступа к instance-переменным вручную.

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

Создание новых классов

[править | править код]

Все ключевые слова языка Objective-C, отсутствующие в С, начинаются с символа @.

Как и в C++, описание класса и его реализация разделены (обычно описание помещается в заголовочные файлы с расширением h, а реализации — в файлы с расширением m).

Ниже приводится общая структура описания нового класса:

@interface ClassName : SuperClass
{
    instance variable declarations
}

method declarations
@end

В версии runtime от Apple все классы имеют общего предка — класс NSObject, содержащий целый ряд важных методов.

Описание переменных ничем не отличается от описания переменных в структурах в языке С:

Если у вас не Apple, то скорее всего вместо NSObject вам потребуется Object (#import <objc/Object.h>).

@interface Rect : NSObject
{
	float     width;
	float     height;
	BOOL      isFilled;
	NSColor * color;
}
@end

Описания же методов заметно отличаются от принятых в C++ и очень сильно похожи на описания методов в языке Smalltalk.

Каждое описание начинается со знака плюс или минус. Знак плюс обозначает, что данный метод является методом класса (то есть его можно посылать только class object’у, а не экземплярам данного класса). Фактически методы класса являются аналогами статических методов в классах в языке C++.

Знак минус служит для обозначения методов объектов — экземпляров данного класса. Обратите внимание, что в 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 и height).

Тип возвращаемого методом значения указывается в круглых скобках сразу же после знака плюс или минус (но перед названием метода). Если тип не указан, то считается, что возвращается значение типа 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"

@implementation ClassName

    method implementations

@end

Ниже приводится пример реализации методов класса Rect, описанного выше.

#import "Rect.h"

@implementation Rect

+ newRect
{
	Rect * rect = [[Rect alloc] init];

	[rect setWidth:  1.0f];
	[rect setHeight: 1.0f];
	[rect setX: 0.0f y: 0.0f];

	return rect;
}

- (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-переменные. Однако, как и в C++, есть возможность управлять видимостью переменных (видимостью методов управлять нельзя) при помощи директив @private, @protected и @public (действующих полностью аналогично языку C++).

@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 для его родительского класса и т. д.

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

В противном случае объекту дается последний шанс обработать сообщение перед вызовом исключения — селектор сообщения вместе с параметрами «заворачивается» в специальный объект типа NSInvocation и объекту посылается сообщение forwardInvocation:, где в качестве параметра выступает объект класса NSInvocation.

Если объект поддерживает forwardInvocation:, то он может либо сам обработать посылаемое сообщение, либо переслать другому объекту для обработки:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ( [someOtherObject respondsToSelector: [anInvocation selector]] )
        [anInvocation invokeWithTarget: someOtherObject];
    else
       ..........
}

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

Тип селектора обозначается как SEL и существует ряд функций и конструкций, позволяющих осуществлять преобразование имени в селектор и обратно.

Так для получения селектора сообщения непосредственно по имени служит конструкция @selector():

   SEL setWidth = @selector(setWidth:);
   SEL setPos   = @selector(setPosition:y:);

Для получения селектора по строке символов (на этапе выполнения) и перевода селектора в строку служат функции NSSelectorFromString и NSStringFromSelector:

   SEL        setWidth   = NSSelectorFromString ( @"setWidth:" );
   NSString * methodName = NSStringFromSelector  ( 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 родительского объекта.

Таким образом, посылая сообщения super мы тем самым вызываем старые версии методов, переопределенные данным классом.

В языке 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 содержит полноценную поддержку протоколов (это аналог интерфейса в Java и абстрактного класса в 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 exc )
 {
    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 )
    {
         // perform modifications to shared objects
         . . .
    }
}

В качестве мьютекса (то есть параметра инструкции @synchronized) рекомендуется указывать объект, недоступный извне, поскольку это может привести к взаимной блокировке, если один и тот же объект используется в качестве мьютекса двумя взаимозависимыми потоками. В частности, не рекомендуется @synchronized(self).

Создание и уничтожение объектов

[править | править код]

В самом языке 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 возникает достаточно часто.

Обратите внимание, что метод(ы) init является обычным методом, ничем не выделяющимся среди остальных (в отличие от C++, где конструктор — это особый метод, у которого, например, нельзя взять адрес).

Поэтому при создании нового класса и метода init вызов переопределенного метода init (при помощи [super init]) должен быть произведен явно в самом начале метода.

Довольно часто у объектов бывает сразу несколько методов, начинающихся с init, например init, initWithName:, initWithContentsOfFile: и т. д.

Установившейся практикой в таком случае является выделение среди всех init-методов одного, называемого designated initializer. Все остальные init-методы должны вызывать его и только он вызывает унаследованный init метод.

- (id) initWithName: (const char *) theName   // designated initializer
{
    self = [super init];                        // call inherited method
    if (self)
    {
        name = strdup ( theName );
    }
    return self;
}

- (id) init
{
    return [self initWithName: ""];
}

В ряде случаев оказывается удобным совместить выделение памяти и инициализацию объекта в один метод (класса), например в классе NSString есть ряд методов класса, возвращающих уже готовый (проинициализированный) объект:

+ (id) stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc
+ (id) stringWithFormat:(NSString *)format, ...

Mac OS X (как и NextStep) для управления временем жизни объектов используют reference counting — каждый объект содержит внутри себя некоторый счетчик, при создании устанавливаемый в единицу.

Посылка объекту сообщения retain увеличивает значение этого счетчика на единицу (так, все контейнерные классы библиотеки Foundation при помещении в них объекта посылают ему сообщение retain).

Установившейся практикой является посылка объекту сообщения retain всеми заинтересованными в нём сторонами (объектами), то есть, если вы запоминаете ссылку на объект, то следует послать ему сообщение retain.

Когда объект перестает быть нужен, ему просто посылается сообщение release.

Данное сообщение уменьшает значение счетчика на единицу и, если это значение стало меньше единицы, уничтожает данный объект.

Перед уничтожением объекта ему посылается сообщение dealloc, позволяющее объекту произвести свою деинициализацию. При этом это также является обычным сообщением и в нём вы явно должны в конце вызвать унаследованную реализацию через [super dealloc].

- (void) dealloc
{
    . . .
    [super dealloc];
}

Управление памятью

[править | править код]

Базовые принципы

[править | править код]

Управление памятью в Objective-C базируется на принципе «владения объектом». Основные правила управления памятью в Objective-C можно записать так:

  • Для получения объекта во владение необходимо вызвать метод, содержащий в названии «alloc», «new» либо «copy». Например, alloc, newObject, mutableCopy.
  • Для освобождения объекта, который был получен при помощи перечисленных выше функций, необходимо вызвать функцию «release» либо «autorelease». Во всех остальных случаях освобождение объекта не требуется.
  • Если полученный объект должен быть сохранен, необходимо либо стать его владельцем (вызвав retain), либо создать его копию (вызов, содержащий в названии «copy»).

Данные правила базируются на соглашении по именованию в Objective-C и, в то же время, сами являются основой этого соглашения.

Базовые принципы на практике

[править | править код]

Предположим, в программе существует класс Company, у которого есть метод workers.

@interface Company : NSObject
{
    NSArray *workers;
}
-(NSArray*)workers;
@end

Рассмотрим небольшой пример использования такого класса:

Company *company = [[Company alloc] init];
// ...
NSArray *workers = [company workers];
// ...
[company release];

Так как объект класса Company создается явно, он должен быть удален по окончании использования ([company release]). В то же время, название метода workers не говорит о том, кто должен удалять массив. В такой ситуации считается, что списком работников управляет объект Компания и его удалять не требуется.

Convenience конструкторы

Многие классы позволяют совместить создание объекта с его инициализацией при помощи методов, называемых convenience конструкторы; такие методы обычно называются +className… Можно предположить, что вызывающая сторона ответственна за управление временем жизни объекта, но подобное поведение противоречило бы соглашению по именованию в Objective-C.

Company *company = [Company company];
[company release];

В приведенном коде вызов [company release] недопустим, так как в данном случае управление временем жизни объекта должно осуществляться при помощи autorelease-пула.

Ниже приводится пример корректной реализации метода company:

+(Company*)company
{
     id ret = [[Company alloc] init];
     return [ret autorelease];
}
autorelease

Вернемся к методу workers класса Company. Так как возвращается массив, временем жизни которого вызывающая сторона не управляет, реализация метода workers будет выглядеть приблизительно так:

-(NSArray*)workers
{
     NSArray *copy = [[NSArray alloc] initWithArray:workers];
     return [copy autorelease];
}

Вызов autorelease добавляет объект copy в autorelease-пул, вследствие чего возвращаемый объект получит сообщение release при удалении пула, в который он был добавлен. Если объекту, добавленному в autorelease-пул, послать сообщение release самостоятельно, при удалении autorelease-пула возникнет ошибка.

Возвращение объекта по ссылке

В ряде случаев объекты возвращаются по ссылке, например, метод класса NSData initWithContentsOfURL:options: error: в качестве параметра error принимает (NSError **)errorPtr. В этом случае так же работает соглашение по именованию, из которого следует, что явного запроса на владение объектом нет, соответственно, удалять его не требуется.

Удаление объектов

Когда счетчик ссылок объекта становится равным нулю, объект удаляется. При этом у объекта вызывается метод -(void)dealloc. Если в объекте содержатся какие-то данные, их необходимо удалить в этой функции.

-(void)dealloc
{
    [workers release];
    [super dealloc];
}

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

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

Autorelease-пул используется для хранения объектов, которым будет послано сообщение release при удалении пула. Для того, чтобы добавить объект в autorelease-пул, ему необходимо отправить сообщение autorelease.

В приложениях Cocoa autorelease-пул всегда доступен по умолчанию. Для не-AppKit приложений необходимо создавать и управлять временем жизни autorelease-пула самостоятельно.

Autorelease-пул реализуется классом NSAutoreleasePool.

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Company *company = [Company company];
    NSArray *workers = [company workers];

    [pool drain];
    return 0;
}

Удалить объекты из autorelease-пула можно не только посредством отправки пулу сообщения release, но и с помощью сообщения drain. Поведение release и drain в среде с подсчетом ссылок идентично. Но в случае работы в GC среде drain вызывает функцию objc_collect_if_needed.

Autorelease-пул в многопоточной среде

В Cocoa для каждого из потоков создается свой собственный autorelease-пул. По завершении потока autorelease-пул уничтожается и всем содержащимся в нём объектам посылается сообщение release.

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

Копирование объектов

[править | править код]

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

Копии бывают двух видов: поверхностная копия (shallow copy) и полная копия (deep copy). Разница между этими копиями состоит в том, что при создании поверхностной копии копируются не данные, а ссылка на объект с данными. В случае полной копии копируется объект с данными.

Пример реализации

Реализация копирования может различаться в зависимости от того, поддерживает ли класс-родитель протокол NSCopying. Пример кода для ситуации, когда родитель не реализует протокол NSCopying:

@interface Company : NSObject <NSCopying>
{
     NSString *name;
}
@property(retain) NSString *name;
-(id)copyWithZone:(NSZone *)zone;
@end

@implementation Company
@synthesize name;
-(id)copyWithZone:(NSZone *)zone
{
     id copy = [[[self class] allocWithZone:zone] init];
     [copy setName:[self name]];
     return copy;
}
@end

Если родитель поддерживает протокол NSCopying, реализация будет несколько иной: вызов allocWithZone заменяется на copyWithZone.

id copy = [super copyWithZone:zone];
Копирование неизменяемых объектов

Для immutable объектов создание копии нецелесообразно, и можно ограничиться отправкой самому себе сообщения retain.

-(id)copyWithZone:(NSZone *)zone
{
     return [self retain];
}

Язык Objective-C обладает возможностью добавлять новые методы к уже существующим классам. Аналогичной возможностью обладают языки Ruby, C#, JavaScript и другие. При этом не требуется исходников класса и добавленные методы автоматически становятся доступными всем классам, унаследованным от изменяемого. Так можно добавить новый метод классу NSObject и этот метод автоматически добавится во все остальные классы.

Механизм, позволяющий расширять уже существующие классы (путём добавления новых методов, новые instance-переменные добавить таким образом нельзя), называется категорией.

Категория имеет своё имя, список методов и имя класса, который она расширяет. Описание категории имеет следующий вид:

#import "ClassName.h"
@interface ClassName ( CategoryName )
  methods declarations
@end

Реализация категории выглядит следующим образом:

#import "CategoryName.h"
@implementation ClassName ( CategoryName )
  methods bodies
@end

С помощью категорий можно создавать свойства (property), которые будут доступны только для чтения другим классам и readwrite внутри своего класса:

@interface ClassName
{
  BOOL flag;
}
@property (assign, readonly) BOOL flag;
@end

#import "ClassName"
@implementation ClassName () // Пустая категория
@property (assign, readwrite) BOOL flag;
@end

@implementation ClassName
@synthesize flag;

-(void) someAction
{
  self.flag = YES;
}
@end

Кроме всего прочего категории можно использовать для того, чтобы обеспечить реализацию классом какого-либо нового протокола, например:

@protocol Printable // сущности, которые можно распечатать
-(void) print;
@end

@interface NSString(Printable) <Printable> // добавляем системному классу NSString возможность быть распечатанным
@end

@implementation NSString(Printable) // реализуем новую функциональность
-(void) print
{
  NSLog(@"Меня распечатали %@!", self);
}
@end

Это избавляет от необходимости писать класс-адаптер PrintableString для NSString.

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 * methodList;

    //
    // Each call to class_nextMethodList returns one methodList
    //

    methodList = class_nextMethodList( classObject, &iterator  )

    while ( methodList != nil )
    {
        // …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 );

Официальный сайт Apple[3] — главный источник информации о языке. Форум разработчиков, примеры кода и полная версия документации доступны только зарегистрированным разработчикам.

IDE Xcode — основное средство разработки на языке Objective-C. IDE поддерживает только ОС Mac OS X и распространяется бесплатно через магазин приложений Apple App Store.

Полезную информации по языку Objective-C можно найти в news-группе[4] и архивах списка рассылки[5].

Проект GNUstep[6] — попытка создания аналогов закрытых библиотек Foundation и AppKit, используемых в NextStep и Mac OS X. Исходный код библиотек написан на языке Objective-C и распространяется свободно. На сайте проекта доступны примеры использования языка и исходный код нескольких приложений.

Objective-C доступен практически в каждом дистрибутиве GNU/Linux благодаря компилятору gobjc, созданному проектом gcc.

Для работы с Objective-C под ОС Windows используют эмуляторы среды POSIX (бесплатные):

Примечания

[править | править код]
  1. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtVersionsPlatforms.html
  2. Defining Classes. Objective-C Classes Are also Objects. developer.apple.com. — «The typical use for a class method is as a factory method». Дата обращения: 21 декабря 2019. Архивировано 12 ноября 2020 года.
  3. Apple developer. Дата обращения: 29 сентября 2017. Архивировано 25 февраля 2011 года.
  4. comp.lang.objective-c. Дата обращения: 14 июня 2007. Архивировано 15 ноября 2006 года.
  5. Objc-language. Дата обращения: 17 декабря 2007. Архивировано из оригинала 18 декабря 2007 года.
  6. Официальный сайт проекта GNUstep. Дата обращения: 15 мая 2022. Архивировано 26 января 2021 года.

Литература

[править | править код]
  • Мэтт Нойбург. Программирование для iOS 7. Основы Objective-C, Xcode и Cocoa = iOS 7 Programming Fundamentals: Objective-C, Cocoa, and Xcode Basics. — М.: «Вильямс», 2014. — 384 с. — ISBN 978-5-8459-1895-6.
  • Скотт Кнастер, Вакар Малик, Марк Далримпл. Objective-C и программирование для Mac OS X и iOS, 2-е издание = Learn Objective-C on the Mac: For OS X and iOS, 2nd edition. — М.: «Вильямс», 2013. — 416 с. — ISBN 978-5-8459-1826-0.
  • Майкл Приват, Роберт Уорнер. Разработка приложений для Mac OS X Lion. Программирование на Objective-C в Xcode = Beginning Mac OS X Lion Apps Development. — М.: Вильямс, 2012. — 384 с. — ISBN 978-5-8459-1789-8.
Ресурсы
Статьи