Различия между C++ и Objective-C не столь уж и велики, тем более что теперь референсную реализацию Objective-C предоставляет компилятор clang, в котором парсер и библиотека семантических проверок обрабатывают оба языка разом.
Ну а раз такое дело, то я постараюсь написать введение в Objetive-C в пределах одного поста. Вам потребуется окружение для разработки на Objective-C: система Mac OS X и XCode. Возникающие по ходу дела вопросы легко гуглятся в английском переводе на stackoverflow либо в документации от Apple.
Кстати, в Objective-C принято работать с указателями на объекты. В C++, напротив, этого избегают по возможности с помощью RAII.
В сообщениях с несколькими аргументами имя разбивается на несколько кусков. Вот как используется сообщение setObject:forKey:
Ну а раз такое дело, то я постараюсь написать введение в Objetive-C в пределах одного поста. Вам потребуется окружение для разработки на Objective-C: система Mac OS X и XCode. Возникающие по ходу дела вопросы легко гуглятся в английском переводе на stackoverflow либо в документации от Apple.
Синтаксические мелочи
Считается, что в ObjC код не «вызывает метод объекта», а «отправляет сообщение». Семантически разница только в добавлении утиной типизации («ducky typing») и «nil object», об этом — ниже. Но есть и синтаксическое различие — вместо круглых скобок в конце имени метода ставятся квадратные скобки вокруг объекта и сообщения, а точка заменяется пробелом
#import <Foundation/Foundation.h> #include <vector> #include <string> void foo1() { std::string cxxString = "text"; const int cxxLength = cxxString.length(); NSString *nsString = @"text"; // @"" - литерал, создающий (NSString *) const int nsLength = [nsString length]; }
Кстати, в Objective-C принято работать с указателями на объекты. В C++, напротив, этого избегают по возможности с помощью RAII.
В сообщениях с несколькими аргументами имя разбивается на несколько кусков. Вот как используется сообщение setObject:forKey:
#import <Foundation/Foundation.h> #include <vector> #include <string> void foo3() { NSMutableDictionary *dict = [[NSMutableDictionary new] autorelease]; [dict setObject:@"string object" forKey:@"it's key"]; }Примечание: Есть более хитрые и одновременно редкие конструкции: перечисление нескольких аргументов в одном куске сообщения через запятую или пустая часть имени сообщения, состоящая из одного лишь двоеточия.
Протоколы и интерфейсы
Ключевое слово @interface используется там, где в других языках — class. Слово @protocol пишут там, где в других языках interface (в C++ — класс с чисто виртуальными методами). Есть возможность дополнить чужой класс своими методами с помощью категорий («categories»). Кроме того, каждому @interface соответствует один @implementation, и в отличие от C++ можно перегружать методы базовых классов и добавлять новые прямо внутри @implementation, без декларирования в @interface.
Утиная типизация
Это необычное имя пошло от фразы, в переводе звучащей так:
Когда я вижу птицу, которая ходит как утка, плавает как утка и даже крякает как утка, я зову её уткой.
Воспринимать название надо буквально =). Если у объекта есть методы сообщения objectiAtIndex: и count, то этот объект сойдёт за NSArray. Вы можете явно привести тип такого объекта к NSArray, и работать с ним как с NSArray.
Примечание: если вам придётся переписывать такой «утиный» код на C++, воспользуйтесь рефакторингом extract interface (скорее всего, придётся сделать его вручную). То есть определите набор используемых методов и создайте интерфейс, а свои классы унаследуйте от него; передавайте указатель/ссылку на интерфейс вместо указателя на MyArray. В крайнем случае такое не прокатит и придётся дублировать метод для MyArray и MyPseudoArray (или использовать шаблоны, в которых тоже действует утиная типизация).
Примечание: если вам придётся переписывать такой «утиный» код на C++, воспользуйтесь рефакторингом extract interface (скорее всего, придётся сделать его вручную). То есть определите набор используемых методов и создайте интерфейс, а свои классы унаследуйте от него; передавайте указатель/ссылку на интерфейс вместо указателя на MyArray. В крайнем случае такое не прокатит и придётся дублировать метод для MyArray и MyPseudoArray (или использовать шаблоны, в которых тоже действует утиная типизация).
Nil object
Объект nil принимает любое сообщение и возвращает nil (который автоматически преобразуется в 0 или false при необходимости).
Примечание: Для долговечных систем это не самый удачный подход — если вы по ошибке инициализируете NSArray* нулём и начнёте с ним работать, программа не упадёт, но и работать правильно не будет. К тому же, разыменование нулевого указателя фатально лишь для одного потока, а программа ещё может воскреснуть (как это делают LLVM и clang). Но для быстрой разработки это позволяет писать меньше кода. Проблемы тут нет, сама Apple использует C и C++ для написания сложной логики.
Счётчик ссылок
Как вы уже догадались, в Objective-C строки и другие сущности являются объектами. Также они наследуются от NSObject и могут принимать соответствующие сообщения, такие как isKindOfClass:.
Семантически работа с объектами ObjC не отличается от работы с std::shared_ptr. Технически, разница есть: счётчик ссылок хранится внутри объекта, а его увеличение или уменьшение можно выполнять явно сообщением retain или release. Для лучшего понимания я покажу, как сделать похожий счётчик ссылок на C++:
class RefCountedBase { public: void retain() { ++m_refCount; } void release() { --m_refCount; if (0 == m_refCount) { delete this; } } protected: RefCountedBase() : m_refCount(1) { } private: int m_refCount; RefCountedBase(const RefCountedBase &) = delete; RefCountedBase &operator=(const RefCountedBase&) = delete; };В ObjC это немного сложнее, чем в примере:
- Сообщения retain/release возвращают id (≈NSObject*), так что благодаря ducky typing их можно писать внутри выражений.
- Есть сообщение autorelease, которое вызывает отложенное уменьшение счётчика — ваш объект будет жить, пока не вы не вернёте управление системному коду, вызвавшему ваш код. Например, в основном потоке крутится цикл обработки сообщений (что типично для GUI приложений), и объект не будет удалён до возвращения к этому циклу.
- Есть сообщение copy, копирующее объект. При этом счётчик ссылок будет равен единице.
For ... in
В ObjC есть цикл for-in, дествующий аналогично range-based for из C++ 2011:
#import <Foundation/Foundation.h> #include <vector> #include <string> void foo2() { std::vector<std::string> cxxArray = {"a", "b", "c"}; for (auto s : cxxArray) std::cout << "string from `cxxArray': " << s; NSArray *nsArray = [NSArray arrayWithObjects:@"a", @"b", @"c", nil]; for (NSString *s in nsArray) NSLog(@"string from `nsArray': %@", s); }
Остальные фишки
- Объявления @property, которые заодно являются аналогами умных указателелей. Примечание: с недавних пор можно забыть о @synthesize, т.к. реализации свойств создаются автоматически. Но могут быть перезаписаны.
- Блоки, аналогичные лямбдам и std::function из c++11
- Тип id, способный хранить указатель на любой объект Objective-C; обычно используется как auto из C++ 2011.
- Сообщение isKindOfClass, похожее на dynamic_cast
Комментариев нет:
Отправить комментарий