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

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

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

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

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

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


person Leo Natan    schedule 22.11.2013    source источник
comment
Я не думаю, что можно использовать [NSBlock alloc] для создания блока, поэтому они не вызываются.   -  person Bryan Chen    schedule 22.11.2013


Ответы (3)


tl;dr

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


обсуждение

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

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

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

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

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

Например, для буквального

^ { 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: литералы напрямую транслируются компилятором в структуры.

person Gabriele Petronella    schedule 22.11.2013
comment
Я не верю, что глобальные блоки создаются в куче; документ, на который вы ссылаетесь, ясно показывает (сразу после фрагмента кода, который вы вытащили), что блок будет блоком стека. (Что имеет смысл; зачем вам помещать его в кучу, если у него нет данных времени выполнения?) Кроме того, я не верю, что это правда, что это единственные объекты target-c, которые выделены в стеке; в 64-битных ABI весь объект может быть встроен в ваш указатель; поэтому они могут быть даже просто распределены по регистру. :) - person Jesse Rusak; 22.11.2013
comment
Глобальные блоки @JesseRusak очень похожи на литералы NSString. Они живут в фиксированном месте, поскольку являются постоянными объектами. Создание их в стеке потребовало бы выделения их каждый раз при построении фрейма стека, но это оптимизировано благодаря тому, что они не содержат никаких ссылок на окружающий контекст. Это хорошо объясняется здесь: cocoawithlove.com/2009/ 10/how-blocks-are-implemented-and.html - person Gabriele Petronella; 22.11.2013
comment
@JesseRusak по поводу 64-битной проблемы, хотя это можно сделать, у меня нет никаких упоминаний о том, что это действительно было сделано. Насколько мне известно, все объекты Objective-C живут исключительно в куче, за исключением блоков. Конечно, я хотел бы оказаться неправым, если у вас есть дополнительные ссылки, которые вы можете предоставить :) - person Gabriele Petronella; 22.11.2013
comment
@GabrielePetronella Спасибо! Вот от этого я и опасался. Я начал небольшой эксперимент, чтобы помочь себе в отладке, где я делал снимки символов стека вызовов для каждого выделения объекта. Веселые вещи. Тоже отлично работает, даже создает историю для скопированных объектов. За исключением чертовых блоков. - person Leo Natan; 22.11.2013
comment
@GabrielePetronella Я по-прежнему возражаю против вашего утверждения, что блок квалифицируется как глобальный блок, поэтому он будет создан в куче - глобальные блоки не выделяются в куче; фиксированное местоположение, в котором они живут, не находится в куче. - person Jesse Rusak; 22.11.2013
comment
@GabrielePetronella Что касается 64-битных указателей на объекты с тегами, вы можете найти ссылки на них в этом посте, хотя я уверен, что где-то есть что-то более авторитетное.. - person Jesse Rusak; 22.11.2013
comment
@JesseRusak спасибо за ссылку, но я все еще защищаю свое предложение: вы не можете размещать объекты Objective-C в стеке. Теговые указатели — это внутренняя оптимизация для конкретной платформы, поэтому я чувствую, что здесь мы касаемся двух очень разных уровней абстракции. - person Gabriele Petronella; 22.11.2013
comment
Правильный акцент на том, что вы не можете размещать объекты Objective-C вне кучи. Компилятор и среда выполнения используют глобальное хранилище и хранилище стека в нескольких местах, таких как блочные объекты, постоянные строковые объекты и объекты с теговыми указателями. Но вы не можете сделать то же самое без экзотических трюков. - person Greg Parker; 22.11.2013
comment
@GregParker Ради интереса, не могли бы вы поделиться информацией об экзотических трюках? Спасибо - person Leo Natan; 23.11.2013
comment
Трюки обычно начинаются с (1) выделения памяти с помощью alloca(), (2) использования object_setClass() для установки isa в этой памяти. Дополнительные сложности потребуются, если какой-либо код вызывает -retain для этого объекта. - person Greg Parker; 06.12.2013

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

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

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

person Greg Parker    schedule 22.11.2013
comment
Привет, так глобальное хранилище не равно стеку? - person Unheilig; 03.01.2014

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

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

person Chuck    schedule 22.11.2013