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