пятница, 12 апреля 2013 г.

Введение в Objective-C для знатоков C++

Различия между C++ и Objective-C не столь уж и велики, тем более что теперь референсную реализацию Objective-C предоставляет компилятор clang, в котором парсер и библиотека семантических проверок обрабатывают оба языка разом.

Ну а раз такое дело, то я постараюсь написать введение в 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 (или использовать шаблоны, в которых тоже действует утиная типизация).

 

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, копирующее объект. При этом счётчик ссылок будет равен единице.
С недавних пор появился режим автоматического подсчёта ссылок, который может потребовать изменений в коде. Во всяком случае, retain/release/autorelease вызывать в этом режиме уже нельзя.

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

Комментариев нет:

Отправить комментарий