O que significa a palavra-chave "__block"?

445

O que exatamente significa a __blockpalavra-chave em Objective-C? Eu sei que permite modificar variáveis ​​dentro de blocos, mas eu gostaria de saber ...

  1. O que exatamente isso diz ao compilador?
  2. Faz mais alguma coisa?
  3. Se isso é tudo o que faz, por que é necessário em primeiro lugar?
  4. Está nos documentos em algum lugar? (Não consigo encontrar).
mjisrawi
fonte
3
verifique aqui e a seção "Blocos e variáveis".
1
Código @ Monkey: Eu estava perguntando especificamente sobre a palavra-chave, não a sintaxe em geral. Portanto, não pense que é realmente uma duplicata.
Mjisrawi
3
@ Monkey Monkey: Não, isso não é uma duplicata. A pergunta que você menciona não fala __blocknada.
DarkDust
3
E se alguém está se perguntando como os Objective-C __blockdevem ser traduzidos para Swift: ”Os fechamentos [no Swift] têm semânticas de captura semelhantes aos blocos [no Objective-C], mas diferem de uma maneira importante: as variáveis ​​são mais mutáveis ​​do que copiadas. Em outras palavras, o comportamento de __block no Objective-C é o comportamento padrão para variáveis ​​no Swift. ” Do livro da Apple: Usando Swift com cacau e Objective-C (Swift 2.2).
Jari Keinänen

Respostas:

542

Diz ao compilador que qualquer variável marcada por ele deve ser tratada de maneira especial quando é usada dentro de um bloco. Normalmente, as variáveis ​​e seu conteúdo que também são usados ​​nos blocos são copiados, portanto, qualquer modificação feita nessas variáveis ​​não aparece fora do bloco. Quando marcadas __block, as modificações feitas dentro do bloco também são visíveis fora dele.

Para obter um exemplo e mais informações, consulte O tipo de armazenamento __block nos tópicos de programação de blocos da Apple .

O exemplo importante é este:

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'
}

Neste exemplo, ambos localCountere localCharactersão modificados antes que o bloco seja chamado. No entanto, dentro do bloco, apenas a modificação para localCharacterseria visível, graças à __blockpalavra - chave. Por outro lado, o bloco pode modificar localCharactere essa modificação é visível fora do bloco.

DarkDust
fonte
8
Excelente, explicação concisa e um exemplo muito útil. Obrigado!
Evan Stone
1
Como o aBlock modifica localCounter? Parece apenas modificar o CounterGlobal. Obrigado
CommaToast
8
Não modifica localCounter, mas modifica localCharacter. Além disso, preste atenção ao valor que localCountertem no bloco: é 42, mesmo que a variável seja aumentada antes que o bloco seja chamado, mas depois que o bloco foi criado (foi quando o valor foi "capturado").
DarkDust 15/10
1
Essa é uma explicação útil - porém - você pode explicar o que parecem ser declarações contraditórias em sua explicação? Você diz acima que "o aBlock modifica ... localCounter" e, nos comentários, diz "[aBlock] NÃO modifica localCounter". Qual é? Se "não for modificado", sua resposta deve ser editada?
precisa
2
Em geral, vars sem __block seriam capturados por valor e compactados no "ambiente" do bloco, quando o bloco for criado. Mas os __block vars não serão capturados, sempre que forem usados ​​dentro ou fora de um bloco, eles serão referenciados como estão.
jchnxu
27

O @bbum aborda os blocos em profundidade em uma postagem do blog e aborda o tipo de armazenamento __block.

__block é um tipo de armazenamento distinto

Assim como estático, automático e volátil, __block é um tipo de armazenamento. Diz ao compilador que o armazenamento da variável deve ser gerenciado de maneira diferente.

...

No entanto, para variáveis ​​__block, o bloco não retém. Cabe a você reter e liberar, conforme necessário.
...

Quanto aos casos de uso que você encontrará, __blockàs vezes é usado para evitar ciclos de retenção, pois não retém o argumento. Um exemplo comum é usar self.

//Now using myself inside a block will not 
//retain the value therefore breaking a
//possible retain cycle.
__block id myself = self;
Joe
fonte
Consulte esta publicação para obter mais informações sobre a questão do ciclo de retenção: benscheirman.com/2012/01/… . __weakTambém seria suficiente neste caso específico? Talvez esteja mais claro ...
Hari Karam Singh
17
Finalmente, a afirmação de que __block pode ser usada para evitar ciclos de referência fortes (também conhecido como ciclos de retenção) está totalmente errada no contexto do ARC. Devido ao fato de que no ARC __block faz com que a variável seja fortemente referenciada, é mais provável que ela as cause. stackoverflow.com/a/19228179/189006
Krishnan
10

Normalmente, quando você não usa __block, o bloco copia (retém) a variável; portanto, mesmo se você modificar a variável, o bloco terá acesso ao objeto antigo.

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

Nestes 2 casos, você precisa de __block:

1.Se você deseja modificar a variável dentro do bloco e esperar que seja visível fora:

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

2.Se você deseja modificar a variável depois de declarar o bloco e espera que o bloco veja a alteração:

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

__block é um qualificador de armazenamento que pode ser usado de duas maneiras:

  1. Marca que uma variável vive em um armazenamento compartilhado entre o escopo lexical da variável original e quaisquer blocos declarados nesse escopo. E o clang irá gerar uma estrutura para representar essa variável e usar essa estrutura por referência (não por valor).

  2. No MRC, __block pode ser usado para evitar reter variáveis ​​de objeto que um bloco captura. Cuidado para que isso não funcione para o ARC. No ARC, você deve usar __weak .

Você pode consultar o documento da apple para obter informações detalhadas.

Mindy
fonte
6

__blocké um tipo de armazenamento usado para tornar variáveis ​​no escopo mutáveis, mais francamente, se você declarar uma variável com esse especificador, sua referência será passada para blocos e não uma cópia somente leitura para obter mais detalhes, consulte Programação de blocos no iOS

mithilesh
fonte
2

espero que isso ajude você

vamos supor que tenhamos um código como:

{
     int stackVariable = 1;

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

dará um erro como "a variável não é atribuível" porque a variável de pilha dentro do bloco é imutável por padrão.

adicionar __block (modificador de armazenamento) antes da declaração torna-o mutável dentro do bloco, ou seja __block int stackVariable=1;

Anurag Bhakuni
fonte
1

Na especificação de idioma do bloco :

Além do novo tipo de bloco, também introduzimos um novo qualificador de armazenamento, __block, para variáveis ​​locais. [testme: uma declaração __block dentro de um literal de bloco] O qualificador de armazenamento __block é mutuamente exclusivo para os qualificadores de armazenamento local existentes auto, register e static. [testme] Variáveis ​​qualificadas por __block agem como se estivessem no armazenamento alocado e esse armazenamento é recuperado automaticamente após o último uso da referida variável. Uma implementação pode escolher uma otimização em que o armazenamento é inicialmente automático e apenas "movido" para o armazenamento alocado (heap) em uma cópia de bloco de um bloco de referência. Tais variáveis ​​podem ser alteradas como são as variáveis ​​normais.

No caso em que uma variável __block é um Bloco, deve-se assumir que a variável __block reside no armazenamento alocado e, como tal, supõe-se que faça referência a um Bloco que também está no armazenamento alocado (que é o resultado de uma operação Block_copy). Apesar disso, não há nenhuma provisão para fazer um Block_copy ou Block_release se uma implementação fornecer armazenamento automático inicial para Blocks. Isso ocorre devido à condição de corrida inerente de potencialmente vários encadeamentos que tentam atualizar a variável compartilhada e à necessidade de sincronização para descartar valores mais antigos e copiar novos. Essa sincronização está além do escopo desta especificação de idioma.

Para obter detalhes sobre o que uma variável __block deve compilar, consulte Especificações de Implementação de Bloco , seção 2.3.

Martin Gordon
fonte
Seus links estão ambos mortos
Ben Leggiero
Esta não é realmente uma resposta e pode ser aprimorada ou removida. Obrigado!
Dan Rosenstark
0

Isso significa que a variável para a qual é um prefixo está disponível para ser usada dentro de um bloco.

Rich Allen
fonte