Что означает ключевое слово «__block»?

447

Что именно означает __blockключевое слово в Objective-C? Я знаю, что это позволяет вам изменять переменные внутри блоков, но я хотел бы знать ...

  1. Что именно он говорит компилятору?
  2. Это делает что-нибудь еще?
  3. Если это все, что нужно, то зачем это нужно?
  4. Это где-нибудь в документах? (Я не могу найти это).
mjisrawi
источник
3
проверьте здесь и раздел «Блоки и переменные».
1
Обезьяна @Code: я спрашивал конкретно о ключевом слове, а не о синтаксисе вообще. Так что не думайте, что это действительно дубликат.
Мисрави
3
@ Код Обезьяна: Нет, это не дубликат. Вопрос, о котором вы упоминаете, вообще не обсуждается __block.
DarkDust
3
И если кому-то интересно, как Objective-C __blockдолжен переводиться в Swift: «Замыкания [в Swift] имеют семантику захвата, аналогичную блокам [в Objective-C], но отличаются одним ключевым способом: переменные изменяются, а не копируются. Другими словами, поведение __block в Objective-C является поведением по умолчанию для переменных в Swift ». Из книги Apple: использование Swift с какао и Objective-C (Swift 2.2).
Яри ​​Кейнянен

Ответы:

545

Он сообщает компилятору, что любая помеченная им переменная должна обрабатываться особым образом, когда она используется внутри блока. Обычно переменные и их содержимое, которые также используются в блоках, копируются, поэтому любая модификация этих переменных не отображается вне блока. Когда они отмечены __block, изменения, сделанные внутри блока, также видны за его пределами.

Пример и дополнительную информацию см. В разделе Тип хранилища __block в темах по программированию блоков Apple .

Важный пример:

extern NSInteger CounterGlobal;
static NSInteger CounterStatic;

{
    NSInteger localCounter = 42;
    __block char localCharacter;

    void (^aBlock)(void) = ^(void) {
        ++CounterGlobal;
        ++CounterStatic;
        CounterGlobal = localCounter; // localCounter fixed at block creation
        localCharacter = 'a'; // sets localCharacter in enclosing scope
    };

    ++localCounter; // unseen by the block
    localCharacter = 'b';

    aBlock(); // execute the block
    // localCharacter now 'a'
}

В этом примере оба localCounterи localCharacterмодифицируются перед вызовом блока. Однако внутри блока localCharacterбудет видна только модификация to , благодаря __blockключевому слову. И наоборот, блок может измениться, localCharacterи эта модификация видна за пределами блока.

DarkDust
источник
8
Отличное, краткое объяснение и очень полезный пример. Спасибо!
Эван Стоун
1
Как aBlock изменяет localCounter? Это только кажется, чтобы изменить CounterGlobal. Спасибо
CommaToast
8
Это не изменяет localCounter, но это изменяет localCharacter. Также обратите внимание на значение localCounterв блоке: оно равно 42, хотя переменная увеличивается до того, как блок вызывается, но после того, как блок создан (именно тогда значение «захватывается»).
DarkDust
1
Это полезное объяснение, хотя вы можете объяснить, что в вашем объяснении кажется противоречивым? Вы говорите выше, что «aBlock изменяет ... localCounter», а затем в комментариях вы говорите «[aBlock] НЕ модифицирует localCounter». Что он? Если это "не изменено", тогда должен ли ваш ответ быть отредактирован?
Praxiteles
2
В общем случае переменные без __block будут захвачены по значению и упакованы в «среду» блока при его создании. Но переменные __block не будут захвачены, всякий раз, когда они используются внутри или снаружи блока, на них ссылаются как есть.
jchnxu
28

@bbum подробно описывает блоки в сообщении блога и затрагивает тип хранилища __block.

__block - это отдельный тип хранилища

__Block - это тип хранения, как статический, автоматический и энергозависимый. Он сообщает компилятору, что хранение переменной должно осуществляться по-другому.

...

Однако для переменных __block блок не сохраняется. Это зависит от вас, чтобы сохранить и отпустить, по мере необходимости.
...

Что касается вариантов использования, которые вы обнаружите __block, иногда используется, чтобы избежать сохранения циклов, поскольку он не сохраняет аргумент. Типичным примером является использование себя.

//Now using myself inside a block will not 
//retain the value therefore breaking a
//possible retain cycle.
__block id myself = self;
Джо
источник
См. Этот пост для получения дополнительной информации о проблеме сохранения цикла: benscheirman.com/2012/01/… . Хватит ли и __weakв этом конкретном случае? Возможно, немного яснее ...
Хари Карам Сингх
17
Наконец, утверждение о том, что __block может использоваться для избежания сильных ссылочных циклов (иначе говоря, сохраняющих циклов), совершенно неверно в контексте ARC. Из-за того факта, что в ARC __block вызывает сильную ссылку на переменную, на самом деле это более вероятно вызывает их. stackoverflow.com/a/19228179/189006
Кришнан
10

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

NSString* str = @"hello";
void (^theBlock)() = ^void() {
    NSLog(@"%@", str);
};
str = @"how are you";
theBlock(); //prints @"hello"

В этих 2 случаях вам нужен __block:

1. Если вы хотите изменить переменную внутри блока и ожидать, что она будет видна снаружи:

__block NSString* str = @"hello";
void (^theBlock)() = ^void() {
    str = @"how are you";
};
theBlock();
NSLog(@"%@", str); //prints "how are you"

2. Если вы хотите изменить переменную после того, как объявили блок и ожидаете, что блок увидит изменение:

__block NSString* str = @"hello";
void (^theBlock)() = ^void() {
    NSLog(@"%@", str);
};
str = @"how are you";
theBlock(); //prints "how are you"
Хамид Вакилиан
источник
8

__block - это спецификатор хранилища, который можно использовать двумя способами:

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

  2. В MRC можно использовать __block , чтобы избежать сохранения переменных объекта , захваченных блоком. Осторожно, это не работает для ARC. В ARC вы должны использовать вместо __weak .

Вы можете обратиться к Apple Doc для подробной информации.

Минди
источник
6

__blockявляется типом хранилища, который используется для того, чтобы сделать переменные области видимости изменяемыми, более откровенно говоря, если вы объявите переменную с помощью этого спецификатора, ее ссылка будет передана в блоки, а не в копию только для чтения. Дополнительные сведения см. в разделе « Программирование блоков в iOS».

mithilesh
источник
2

Надеюсь, что это поможет вам

предположим, у нас есть код вроде:

{
     int stackVariable = 1;

     blockName = ^()
     {
      stackVariable++;
     }
}

это выдаст ошибку типа «переменная не присваивается», потому что переменная стека внутри блока по умолчанию неизменна.

добавление __block (модификатор хранилища) перед объявлением делает его изменяемым внутри блока, т.е. __block int stackVariable=1;

Анураг Бхакуни
источник
1

Из спецификации языка блока :

В дополнение к новому типу блока мы также вводим новый спецификатор хранения __block для локальных переменных. [testme: объявление __block внутри литерала блока] Квалификатор хранилища __block является взаимоисключающим с существующими квалификаторами локального хранилища auto, register и static. [testme] Переменные, квалифицированные с помощью __block, действуют так, как если бы они находились в выделенном хранилище, и это хранилище автоматически восстанавливается после последнего использования указанной переменной. Реализация может выбрать оптимизацию, когда хранилище изначально является автоматическим и только «перемещается» в выделенное (кучное) хранилище после Block_copy ссылающегося блока. Такие переменные могут быть видоизменены как обычные переменные.

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

Подробнее о том, что переменная __block должна компилировать, смотрите в спецификации реализации блока. , раздел 2.3.

Мартин Гордон
источник
Ваши ссылки обе мертвы
Бен Легжеро
Это на самом деле не ответ, и его можно уточнить или удалить. Спасибо!
Дэн Розенстарк
0

Это означает, что переменная, к которой относится префикс, доступна для использования в блоке.

Рич Аллен
источник