Как создаются объекты NSBlock?

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

Как объекты блока создаются внутри среды выполнения Objective C?

Я вижу иерархию классов, которые все представляют различные типы блоков, а наивысший суперкласс в иерархии ниже NSObject равен NSBlock. Демпинг для данных класса показывает, что он реализует методы + alloc, + allocWithZone:, + copy и + copyWithZone:. Ни один из других подклассов блоков не реализует эти методы класса, что заставляет меня поверить, возможно, ошибочно, что NSBlock отвечает за обработку блоков.

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

Итак, я предполагаю, что блоки реализованы по-другому? Кто-нибудь может пролить свет на то, как эта реализация работает? Даже если я не могу подключиться к распределению и копированию блоков, я хотел бы понять внутреннюю реализацию.

Ответ 1

TL;DR

Компилятор непосредственно переводит блок-литералы в структуры и функции. Поэтому вы не видите вызов alloc.


Обсуждение

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

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

Вероятно, из-за этой странности в их распределении разработчики языка решили разрешить создание блоков исключительно через блок-литеры (т.е. используя оператор ^). Таким образом, компилятор полностью контролирует выделение блоков.

Как поясняется в спецификации clang, компилятор автоматически сгенерирует две структуры и по крайней мере одну функцию для каждого литературного блока, с которым он сталкивается:

  • блок литерала struct
  • блок дескриптор структуры
  • функция вызова блока

Например, для литерала

^ { printf("hello world\n"); }

в 32-битной системе компилятор будет создавать следующие

struct __block_literal_1 {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(struct __block_literal_1 *);
    struct __block_descriptor_1 *descriptor;
};

void __block_invoke_1(struct __block_literal_1 *_block) {
    printf("hello world\n");
}

static struct __block_descriptor_1 {
    unsigned long int reserved;
    unsigned long int Block_size;
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1), __block_invoke_1 };

(кстати, этот блок квалифицируется как глобальный блок, поэтому он будет создан в фиксированном месте в памяти)

Таким образом, блоки являются объектами Objective-C, но с низким уровнем: они представляют собой просто структуры с указателем isa. Хотя с формальной точки зрения они являются экземплярами конкретного подкласса NSBlock, API Objective-C никогда не используется для выделения, поэтому почему вы не видите вызов alloc: литералы напрямую транслируются в структуры компилятором.

Ответ 2

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

Объекты блока похожи на объекты с мостом CoreFoundation: интерфейс Objective-C является обложкой для базового интерфейса C. Метод block object -copyWithZone: вызывает функцию _Block_copy(), но некоторые кодовые вызовы _Block_copy() напрямую. Это означает, что точка останова на -copyWithZone: не будет захватывать все копии.

(Да, вы можете использовать объекты блока в обычном C-коде. Там есть функция qsort_b() и функция atexit_b(), и, возможно, это может быть.)

Ответ 3

Блоки - это в основном магия компилятора. В отличие от обычных объектов, они фактически распределяются непосредственно в стеке - они только помещаются в кучу, когда вы их копируете.

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