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

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

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

Если вы никогда не программировали ни на каком языке программирования, перед началом вам следует хотя бы иметь базовое понимание связанных концепций. Использование объектов и объектно-ориентированной архитектуры — это основа программирования для iPhone; понимание того, как они взаимодействуют, очень важно для создания ваших программ. Чтобы изучить объектно-ориентированные концепции, обратитесь к Object-Oriented Programming with Objective-C.

Objective-C: надмножество C

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

Расширения файлов для кода Objective-C

Расширение Тип содержимого
.h Файл заголовка. Файлы заголовков содержат объявления классов, типов, функций и констант.
.m Файл исходного кода. Это типичное расширение для файлов исходного кода; такие файлы могут содержать как код Objective-C, так и код C.
.mm Файл исходного кода. Файл исходного кода с этим расширением может содержать код C++ в дополнение к коду Objective-C и C. Используйте это расширение только тогда, когда вам действительно нужно использовать классы или возможности C++ в вашем коде Objective-C.

Когда вам нужно включить заголовочные файлы в исходный код, вы можете использовать стандартную директиву компиляции #include, но Objective-C предоставляет лучший способ. Директива #import точно такая же, как #include, за исключением того, что она гарантирует, что один и тот же файл включается только один раз. В примерах и документации Objective-C обычно используется #import, и ваш код должен делать то же самое.

Синтаксис

Объектно-ориентированный синтаксис Objective-C происходит от стиля передачи сообщений Smalltalk. Весь остальной необъектно-ориентированный синтаксис, включая типы переменных, препроцессор, управление потоком выполнения, а также объявление и вызов функций, точно такой же, как в C. Однако некоторый допустимый синтаксис C может не выражать то же значение в Objective-C. Например, некоторые логические выражения возвращают true в C, но при прямом сравнении с YES в Objective-C функция завершится ошибкой, потому что YES в Objective-C имеет только значение 1.

Первая программа на Objective-C, основанная на Xcode 4.3.1:

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {

    @autoreleasepool {
        NSLog(@"Hello World!");
    }

   return 0;
}

Передача сообщений

Самая заметная особенность Objective-C — модель передачи сообщений, унаследованная от Smalltalk, которая довольно сильно отличается от распространённого сегодня подхода в стиле C++. В Objective-C правильнее говорить, что объекты передают друг другу сообщения, а не вызывают методы друг у друга. Главное различие между этими двумя стилями заключается в действии вызова методов / передачи сообщений. В C++ связь между классами и методами строгая и однозначная: метод должен принадлежать классу и жёстко привязан на этапе компиляции, поэтому невозможно вызвать метод, которого нет в классе. Но в Objective-C связь между классами и сообщениями более свободная. Вызов метода рассматривается как отправка сообщения объекту, а все методы — как ответы на сообщения. Вся обработка сообщений определяется динамически только во время выполнения, и класс сам решает, как обрабатывать полученные сообщения. То есть класс не гарантированно отвечает на каждое полученное сообщение. Если класс получает сообщение, которое не может обработать, программа лишь выбрасывает исключение; она не завершается с ошибкой и не падает.

В C++ синтаксис отправки сообщения объекту (или вызова метода) выглядит так:

obj.method(argument);

В Objective-C это записывается так:

[obj method: argument];

Эти два варианта различаются не только синтаксически, но и по принципиальному поведению.

Вот простой пример класса car, объясняющий особенность Objective-C по передаче сообщений:

[car fly];

Типичная интерпретация для C++ — «вызвать метод fly класса car». Если класс car не определяет метод fly, компиляция обязательно завершится неудачей. Но в Objective-C это следует интерпретировать как «отправить сообщение fly объекту car». fly — это сообщение, а car — получатель сообщения. Получив сообщение, car решает, как на него ответить. Если класс car определяет метод fly, выполняется код внутри метода; если в классе car нет метода fly, программа всё равно компилируется, а во время выполнения выбрасывается исключение.

У каждого из этих двух стилей есть свои преимущества и недостатки. C++ требует, чтобы у каждого метода было соответствующее действие, а связывание во время компиляции делает вызовы функций очень быстрыми. Недостаток в том, что он может обеспечить лишь ограниченную возможность динамического связывания с помощью ключевого слова virtual. Objective-C по своей природе поддерживает утиную типизацию и динамическое связывание, поскольку сообщения обрабатываются во время выполнения, что позволяет отправлять объектам неизвестные сообщения. Он может отправлять сообщения целой коллекции объектов, не проверяя тип каждого объекта по отдельности, а также имеет механизм пересылки сообщений. Кроме того, нулевой объект nil по умолчанию ничего не делает после получения сообщения, поэтому отправка сообщения nil не приводит к аварийному завершению программы.

Строки

Как надмножество C, Objective-C поддерживает соглашения о строках языка C. То есть отдельные символы заключаются в одинарные кавычки, а строки — в двойные. Однако большинство кода Objective-C обычно не использует строки в стиле C. Вместо этого большинство фреймворков передают строки как объекты NSString. Класс NSString предоставляет класс-обертку для строк, включая все преимущества, которые вы ожидали, такие как встроенное управление памятью для хранения строк произвольной длины, поддержка Unicode, утилиты форматирования в стиле printf и т. д. Поскольку этот тип строк используется так часто, Objective-C предоставляет мнемонику для удобного создания объектов NSString из константных значений. Чтобы использовать эту мнемонику, все, что нужно сделать, — поставить символ @ перед обычной строкой в двойных кавычках, как показано в следующем примере:

NSString* myString = @"My String\n";
NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"];

// 从一个C语言字符串创建Objective-C字符串
NSString*  fromCString = [NSString stringWithCString:"A C string" 
encoding:NSASCIIStringEncoding];

Класс

Как и во всех других объектно-ориентированных языках, классы являются базовой структурой в Objective-C для инкапсуляции данных и поведения, которое работает с этими данными. Объект — это экземпляр класса во время выполнения; он содержит собственную копию в памяти переменных экземпляра, объявленных классом, а также указатели на члены класса. Спецификация класса Objective-C состоит из двух частей: интерфейса и реализации. Часть интерфейса содержит объявление класса, определения переменных экземпляра и методы, относящиеся к классу. Часть реализации содержит фактический код методов класса.

На следующей диаграмме показан синтаксис объявления класса с именем MyClass, который наследуется от базового класса NSObject. Объявление класса всегда начинается с директивы компилятора @interface и заканчивается директивой компилятора @end. После имени класса (отделённого двоеточием) идёт имя родительского класса. Переменные экземпляра (или члены) класса объявляются в блоке кода, заключённом в фигурные скобки. После блока переменных экземпляра идёт список методов, объявленных классом. Каждое объявление переменной экземпляра и метода заканчивается точкой с запятой.

Файлы определения класса следуют соглашениям языка C и имеют суффикс .h; файлы реализации используют суффикс .m.

Диаграмма объявления класса

Интерфейс

Часть интерфейса явно определяет имя, элементы данных и методы класса. Она начинается с ключевого слова @interface и заканчивается @end.

@interface MyObject : NSObject {
    int memberVar1; // 实体变量
    id  memberVar2;
}

+(return_type) class_method; // 类方法

-(return_type) instance_method1; // 实例方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end

Знак +/- перед методом указывает тип функции: плюс (+) обозначает метод класса, который можно вызвать без экземпляра и который похож на статическую функцию-член в C++. Минус (-) обозначает обычный метод экземпляра.

Вот сравнение с аналогичным синтаксисом C++, как показано ниже:

class MyObject : public NSObject {
protected:
    int memberVar1;  // 实体变量
    void * memberVar2;

  public:
    static return_type class_method(); // 類方法

    return_type instance_method1();    // 实例方法
    return_type instance_method2( int p1 );
    return_type instance_method3( int p1, int p2 );
}

Когда Objective-C определяет новый метод, двоеточие (:) в имени обозначает передачу параметров, в отличие от C, где для передачи параметров используются круглые скобки, как в математических функциях. Методы Objective-C позволяют размещать параметры внутри имени, а не добавлять их все в конец имени метода, что повышает читаемость программы. Возьмём в качестве примера метод для установки значений RGB цвета:

- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/

[myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/

Сигнатура этого метода — setColorToRed:Green:Blue:. После каждого двоеточия идёт параметр типа float, представляющий соответственно красный, зелёный и синий.

Реализация

Блок реализации содержит реализации публичных методов, а также определения приватных переменных и методов. Он начинается с ключевого слова @implementation и заканчивается @end.

@implementation MyObject {
  int memberVar3; //私有實體變數
}

+(return_type) class_method {
    .... //method implementation
}
-(return_type) instance_method1 {
     ....
}
-(return_type) instance_method2: (int) p1 {
    ....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
    ....
}
@end

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

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

В Objective-C для создания объекта требуются два сообщения: alloc и init. alloc используется для выделения памяти, а init — для инициализации объекта. И init, и alloc — это методы, определённые в NSObject. Только после того, как родительский объект получит эти два сообщения и правильно на них ответит, новый объект будет полностью создан. Ниже приведён пример:

MyObject * my = [[MyObject alloc] init];

В Objective-C 2.0, если для создания объекта не требуются параметры, можно напрямую использовать new.

MyObject * my = [MyObject new];

Это всего лишь синтаксическое сокращение; результат абсолютно такой же.

Если вы хотите настроить процесс инициализации, вы можете переопределить метод init, чтобы добавить дополнительную работу. (Его назначение аналогично конструктору в C++.)

Методы

Классы в Objective-C могут объявлять два типа методов: методы экземпляра и методы класса. Метод экземпляра — это метод, который выполняется в контексте конкретного экземпляра класса. То есть, прежде чем вызвать метод экземпляра, необходимо сначала создать экземпляр класса. Метод класса, в отличие от него, означает, что создавать экземпляр не нужно.

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

Синтаксис объявления метода

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

Сообщения заключаются в квадратные скобки ([ и ]). Внутри скобок объект, получающий сообщение, находится слева, а сообщение (включая любые параметры, которые требуются сообщению) — справа. Например, чтобы отправить сообщение insertObject:atIndex: переменной myArray, вы должны использовать следующий синтаксис:

[myArray insertObject:anObj atIndex:0];

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

[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];

Хотя все предыдущие примеры отправляют сообщения экземпляру класса, вы также можете отправлять сообщения самому классу. При отправке сообщения классу указанный вами метод должен быть определён как метод класса, а не как метод экземпляра. Можно считать, что методы класса несколько похожи на статические члены классов C++ (но не полностью совпадают).

Типичные способы использования методов класса — в качестве фабричных методов для создания новых экземпляров класса или как способ доступа к общей информации, связанной с классом. Синтаксис объявления методов класса почти точно такой же, как для методов экземпляра, с лишь одним небольшим отличием. В отличие от методов экземпляра, которые используют знак минус в качестве идентификатора типа метода, методы класса используют знак плюс (+).

Следующий пример демонстрирует, как метод класса может служить фабричным методом для класса. Здесь arrayWithCapacity — метод класса NSMutableArray, который выделяет память и инициализирует новый экземпляр класса, а затем возвращает его вам.

NSMutableArray*   myArray = nil; // nil 基本上等同于 NULL

// 创建一个新的数组,并把它赋值给 myArray 变量
myArray = [NSMutableArray arrayWithCapacity:0];

Свойства

Свойства — это удобный способ заменить объявление методов доступа. Свойства не создают новую переменную экземпляра в объявлении вашего класса. Они представляют собой просто сокращённый способ определения методов, которые обращаются к существующим переменным экземпляра. Класс, который предоставляет доступ к переменным экземпляра, может использовать нотацию свойств вместо синтаксиса геттера и сеттера. Классы также могут использовать свойства для предоставления доступа к «виртуальным» переменным экземпляра, которые являются результатом динамического вычисления части данных, а не фактического хранения в переменной экземпляра.

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

Объявления свойств следует размещать там же, где объявления методов в интерфейсе класса. Базовое определение использует директиву компилятора @property, за которой следуют информация о типе и имя свойства. Вы также можете настроить свойство с помощью пользовательских параметров, которые определяют поведение методов доступа. В следующем примере показаны некоторые простые объявления свойств:

@interface Person : NSObject {
    @public
        NSString *name;
    @private
        int age;
}

@property(copy) NSString *name;
@property(readonly) int age;

-(id)initWithAge:(int)age;
@end

Методы доступа свойства реализуются ключевым словом @synthesize, которое автоматически генерирует пару методов доступа из объявления свойства. Альтернативно можно использовать ключевое слово @dynamic, чтобы указать, что методы доступа будут предоставлены вручную программистом.

@implementation Person
@synthesize name;
@dynamic age;

-(id)initWithAge:(int)initAge
{
    age = initAge; // 注意:直接赋给成员变量,而非属性
    return self;
}

-(int)age
{
    return 29; // 注意:并非返回真正的年龄
}
@end

К свойствам можно обращаться с помощью традиционных выражений сообщений, точечных выражений или пары методов "valueForKey:" / "setValue:forKey:".

Person *aPerson = [[Person alloc] initWithAge: 53];
aPerson.name = @"Steve"; // 注意:点表达式,等于[aPerson setName: @"Steve"];
NSLog(@"Access by message (%@), dot notation(%@), property name(%@) and direct instance variable access (%@)",
      [aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);

Чтобы использовать точечные выражения для доступа к свойствам экземпляра, нужно использовать ключевое слово "self":

-(void) introduceMyselfWithProperties:(BOOL)useGetter
{
    NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name)); // NOTE: getter vs. ivar access
}

Свойства класса или протокола можно читать динамически.

int i;
int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount);

for ( i=0; i < propertyCount; i++ ) {
    objc_property_t *thisProperty = propertyList + i;
    const char* propertyName = property_getName(*thisProperty);
    NSLog(@"Person has a property: '%s'", propertyName);
}

Быстрое перечисление

По сравнению с использованием объектов NSEnumerator или последовательным перебором коллекций, Objective-C 2.0 предоставляет синтаксис быстрого перебора. В Objective-C 2.0 следующие циклы функционально эквивалентны, но имеют разные характеристики производительности.

// 使用NSEnumerator
NSEnumerator *enumerator = [thePeople objectEnumerator];
Person *p;

while ( (p = [enumerator nextObject]) != nil ) {
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用依次枚举
for ( int i = 0; i < [thePeople count]; i++ ) {
    Person *p = [thePeople objectAtIndex:i];
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用快速枚举
for (Person *p in thePeople) {
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}

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


Протоколы

Протокол — это список методов без реализаций. Любой класс может принять протокол и реализовать именно этот набор методов.

В эпоху NeXT в Objective-C однажды попытались ввести концепцию множественного наследования, но она не была реализована из-за появления протоколов.

Протоколы похожи на "интерфейсы" в Java и C#. В Objective-C есть два способа определения протоколов: "формальные протоколы", гарантируемые компилятором, и "неформальные протоколы", создаваемые для конкретных целей.

Неформальные протоколыЭто список методов, которые можно реализовать по желанию. Хотя неформальные протоколы называются протоколами, на самом деле это обозначение для нереализованной категории, присоединённой к NSObject. В языковом механизме Objective-C не существует такого понятия, как неформальный протокол. После OS X 10.6 введение ключевого слова @optional дало формальным протоколам ту же возможность, поэтому неформальные протоколы были объявлены устаревшими и больше не используются.

Формальные протоколыПодобно «интерфейсам» в Java, это список методов. Любой класс может объявить, что он реализует определённый протокол. До Objective-C 2.0 класс должен был реализовать все методы в протоколе, соответствие которому он объявил; иначе компилятор выдавал бы ошибку, указывающую, что класс не реализовал все методы протокола, соответствие которому он объявил. Objective-C 2.0 позволяет помечать некоторые методы в протоколе как необязательные, поэтому компилятор не будет требовать реализации этих необязательных методов.

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

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

Синтаксис

Протокол начинается с ключевого слова @protocol как начала блока и заканчивается @end, а между ними находится список методов.

@protocol Locking
- (void)lock;
- (void)unlock;
@end

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

Далее SomeClass объявляет, что принимает протокол Locking:

@interface SomeClass : SomeSuperClass <Locking>
@end

После того как SomeClass объявляет, что принимает протокол Locking, SomeClass обязан реализовать два метода в протоколе Locking.

@implementation SomeClass
- (void)lock {
  // 實現lock方法...
}
- (void)unlock {
  // 實現unlock方法...
}
@end

Поскольку SomeClass уже соответствует протоколу Locking, вызывающий код может безопасно отправлять сообщения lock или unlock переменной экземпляра SomeClass, не беспокоясь о том, что она не сможет ответить на сообщения.

Плагины — ещё один пример использования абстрактных определений, позволяющий определить желаемое поведение, не заботясь о реализации плагина.


Динамическая типизация

Подобно Smalltalk, Objective-C имеет динамическую типизацию: сообщения можно отправлять любой объектной сущности, независимо от того, есть ли в открытом интерфейсе объекта соответствующий метод. По сравнению со статически типизированными языками, такими как C++, компилятор заблокирует вызовы методов по указателям (void*). Но в Objective-C можно отправить любое сообщение id (id во многом похож на void*, но строго ограничен объектами); компилятор лишь выдаёт предупреждение, что «объект может не ответить на сообщение». Программа всё равно может скомпилироваться, а что произойдёт на самом деле, зависит от реальной формы объекта во время выполнения. Если объект действительно может ответить на сообщение, соответствующий метод всё равно выполняется.

После того как объект получает сообщение, у него есть три возможных способа его обработать. Во-первых, он может ответить на сообщение и выполнить метод. Если он не может ответить, он может переслать сообщение другому объекту. Если не сделано ни то, ни другое, он должен обработать исключение, возникающее из-за отсутствия ответа. Пока выполнено одно из трёх, сообщение считается завершённым и отбрасывается. Если сообщение отправляется "nil" (нулевой указатель объекта), сообщение обычно игнорируется; в зависимости от параметров компилятора оно может вызвать исключение.

Хотя Objective-C обладает возможностями динамической типизации, к переменным всё ещё можно применять статическую проверку типов во время компиляции. Следующие три объявления полностью эквивалентны во время выполнения, но каждое предоставляет всё более явную информацию о типе. Дополнительная информация о типе позволяет компилятору проверять типы переменных во время компиляции и выдавать предупреждения для переменных с несоответствующими типами.

Следующие три метода отличаются только формой параметра:

- setMyValue:(id) foo;

Форма id указывает, что параметр "foo" может быть экземпляром любого класса.

- setMyValue:(id <aProtocol>) foo;

id<aProtocol> указывает, что "foo" может быть экземпляром любого класса, но должен принимать протокол "aProtocol".

- setMyValue:(NSNumber*) foo;

Это объявление указывает, что "foo" должен быть экземпляром "NSNumber".

Динамическая типизация — мощная возможность. При реализации классов-контейнеров в статически типизированных языках без обобщений (например, в версиях до Java 5) программистам приходилось писать класс-контейнер для объектов обобщённого типа, а затем постоянно выполнять приведение между обобщённым и фактическим типами. Однако приведение типов нарушает статическую типизацию; например, запись "integer" и чтение его как "string" вызывает ошибку времени выполнения. Такие проблемы решаются с помощью обобщений, но классы-контейнеры требуют согласованных типов для объектов содержимого, тогда как в языках с динамической типизацией такой проблемы вообще нет.


Пересылка

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

Среда выполнения Objective-C определяет пару методов в Object:

Метод пересылки:

- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC
- (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

Метод ответа:

- (retval_t) performv:(SEL) sel :(arglist_t) args;  // with GCC
- (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

Объекту, который хочет реализовать пересылку, нужно лишь переопределить вышеуказанные методы новыми методами, чтобы определить поведение пересылки. Нет необходимости переписывать метод ответа performv::, поскольку этот метод просто отправляет сообщение отвечающему объекту и передаёт параметры. При этом тип SEL — это тип сообщений в Objective-C.

Следующий код демонстрирует основную концепцию пересылки:

Код файла Forwarder.h:

#import <objc/Object.h>

@interface Forwarder : Object
{
    id recipient; //该对象是我们希望转发到的对象。
}

@property (assign, nonatomic) id recipient;

@end

Код файла Forwarder.m:

#import "Forwarder.h"

@implementation Forwarder

@synthesize recipient;

- (retval_t) forward: (SEL) sel : (arglist_t) args
{
    /*
     *检查转发对象是否响应该消息。
     *若转发对象不响应该消息,则不会转发,而产生一个错误。
     */
    if([recipient respondsTo:sel])
       return [recipient performv: sel : args];
    else
       return [self error:"Recipient does not respond"];
}

Код файла Recipient.h:

#import <objc/Object.h>

// A simple Recipient object.
@interface Recipient : Object
- (id) hello;
@end

Код файла Recipient.m:

#import "Recipient.h"

@implementation Recipient

- (id) hello
{
    printf("Recipient says hello!\n");

    return self;
}

@end

Код файла main.m:

#import "Forwarder.h"
#import "Recipient.h"

int main(void)
{
    Forwarder *forwarder = [Forwarder new];
    Recipient *recipient = [Recipient new];

    forwarder.recipient = recipient; //Set the recipient.
    /*
     *转发者不响应hello消息!该消息将被转发到转发对象。
     *(若转发对象响应该消息)
     */
    [forwarder hello];

    return 0;
}

При компиляции с GCC компилятор сообщает:

$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc
main.m: In function `main':
main.m:12: warning: `Forwarder' does not respond to `hello'
$

Как упоминалось ранее, компилятор сообщает, что класс Forwarder не отвечает на сообщение hello. В этом случае, поскольку перенаправление реализовано, это предупреждение можно игнорировать. Запуск программы выводит следующее:

$ ./a.out
Recipient says hello!

Категории

При проектировании Objective-C большое внимание уделяется сопровождению больших программных каркасов. Опыт структурного программирования показывает, что один из основных способов улучшить код — разбить его на более мелкие части. Objective-C заимствует и расширяет концепцию «категории» из реализации Smalltalk, чтобы помочь достичь цели декомпозиции кода.

Категория может разложить реализации методов на ряд отдельных файлов. Программисты могут поместить группу связанных методов в категорию, чтобы сделать программу более читаемой. Например, категорию с именем «spell checking» можно добавить к классу строк, а соответствующий код проверки орфографии можно поместить в эту категорию.

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

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

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

Другие языки также пытались добавить эту языковую возможность разными способами. TOM идёт в этом отношении дальше, позволяя добавлять не только методы, но и переменные-члены. Другие языки используют решения, ориентированные на объявления; наиболее примечательным из них является язык Self.

C# и Visual Basic.NET реализуют похожую функциональность через методы расширения и частичные классы. Ruby и некоторые динамические языки называют этот приём "monkey patch".

Пример использования категорий

В этом примере создаётся класс Integer, который сам определяет только свойство integer, а затем добавляются две категории, Arithmetic и Display, чтобы расширить функциональность класса. Хотя категории могут обращаться к закрытым членам класса, обычно лучше обращаться к ним через методы доступа к свойствам, что делает категории более независимыми от исходного класса. Это типичное использование категорий — другое применение состоит в замене методов в исходном классе с помощью категорий, хотя использовать категории вместо наследования для замены методов не считается хорошей практикой.

Код файла Integer.h:

#import <objc/Object.h>

@interface Integer : Object
{
@private
    int integer;
}

@property (assign, nonatomic) integer;

@end

Код файла Integer.m:

#import "Integer.h"

@implementation Integer

@synthesize integer;

@end

Код файла Arithmetic.h:

#import "Integer.h"

@interface Integer(Arithmetic)
- (id) add: (Integer *) addend;
- (id) sub: (Integer *) subtrahend;
@end

Код файла Arithmetic.m:

#import "Arithmetic.h"

@implementation Integer(Arithmetic)
- (id) add: (Integer *) addend
{
    self.integer = self.integer + addend.integer;
    return self;
}

- (id) sub: (Integer *) subtrahend
{
    self.integer = self.integer - subtrahend.integer;
    return self;
}
@end

Код файла Display.h:

#import "Integer.h"

@interface Integer(Display)
- (id) showstars;
- (id) showint;
@end

Код файла Display.m:

#import "Display.h"

@implementation Integer(Display)
- (id) showstars
{
    int i, x = self.integer;
    for(i=0; i < x; i++)
       printf("*");
    printf("\n");

    return self;
}

- (id) showint
{
    printf("%d\n", self.integer);

    return self;
}
@end

Код файла main.m:

#import "Integer.h"
#import "Arithmetic.h"
#import "Display.h"

int
main(void)
{
    Integer *num1 = [Integer new], *num2 = [Integer new];
    int x;

    printf("Enter an integer: ");
    scanf("%d", &x);

    num1.integer = x;
    [num1 showstars];

    printf("Enter an integer: ");
    scanf("%d", &x);

    num2.integer = x;
    [num2 showstars];

    [num1 add:num2];
    [num1 showint];

    return 0;
}

Скомпилируйте следующей командой:

gcc -x objective-c main.m Integer.m Arithmetic.m Display.m -lobjc

На этапе компиляции можно поэкспериментировать, опустив #import "Arithmetic.h", команду [num1 add:num2] и файл Arithmetic.m. Программа всё равно запустится, что показывает, что категории могут загружаться динамически и по требованию; если функциональность какой-либо категории не нужна, её можно просто не компилировать.


Сборка мусора

Objective-C 2.0 предоставляет необязательный сборщик мусора. В режиме обратной совместимости среда выполнения Objective-C превращает операции подсчета ссылок, такие как "retain" и "release", в пустые операции. Когда сборка мусора включена, все объекты находятся под управлением сборщика. Обычные указатели C можно помечать с помощью "__strong", указывая, что объект, на который указывает указатель, все еще используется. Указатели, помеченные "__weak", не учитываются сборщиком и перезаписываются в "nil", когда объект собирается. Реализация Objective-C 2.0 на iOS не включает сборщик мусора. Сборщик мусора работает в фоновом потоке с низким приоритетом и может приостанавливаться при действиях пользователя, тем самым поддерживая хороший пользовательский опыт.

Ссылка: https://zh.wikipedia.org/wiki/Objective-C