Resposta curta
Em vez de acessar self
diretamente, você deve acessá-lo indiretamente, a partir de uma referência que não será mantida. Se você não estiver usando a Contagem automática de referência (ARC) , poderá fazer o seguinte:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
A __block
palavra-chave marca variáveis que podem ser modificadas dentro do bloco (não estamos fazendo isso), mas também não são retidas automaticamente quando o bloco é retido (a menos que você esteja usando o ARC). Se você fizer isso, deve ter certeza de que nada mais tentará executar o bloco após o lançamento da instância MyDataProcessor. (Dada a estrutura do seu código, isso não deve ser um problema.) Leia mais sobre__block
.
Se você estiver usando o ARC , a semântica das __block
alterações e a referência serão mantidas; nesse caso, você deve declará-lo __weak
.
Resposta longa
Digamos que você tenha um código como este:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
O problema aqui é que o eu está mantendo uma referência ao bloco; enquanto isso, o bloco deve manter uma referência a si próprio para buscar sua propriedade de delegado e enviar um método para o delegado. Se todo o resto do seu aplicativo lançar sua referência a esse objeto, sua contagem de retenção não será zero (porque o bloco está apontando para ele) e o bloco não fará nada de errado (porque o objeto está apontando para ele) e, portanto, o par de objetos vazará para a pilha, ocupando memória, mas inacessível para sempre sem um depurador. Trágico, sério.
Esse caso pode ser facilmente corrigido fazendo isso:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
Nesse código, o self está retendo o bloco, o bloco está retendo o delegado e não há ciclos (visíveis a partir daqui; o delegado pode reter nosso objeto, mas está fora de nossas mãos agora). Esse código não corre o risco de vazar da mesma maneira, porque o valor da propriedade delegate é capturado quando o bloco é criado, em vez de procurado quando é executado. Um efeito colateral é que, se você alterar o delegado após a criação desse bloco, o bloco ainda enviará mensagens de atualização ao antigo delegado. A probabilidade de isso acontecer ou não depende da sua aplicação.
Mesmo se você foi legal com esse comportamento, ainda não pode usar esse truque no seu caso:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Aqui você está passando self
diretamente para o delegado na chamada de método, então você precisa colocá-lo em algum lugar. Se você tiver controle sobre a definição do tipo de bloco, o melhor seria passar o delegado para o bloco como um parâmetro:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
Essa solução evita o ciclo de retenção e sempre chama o delegado atual.
Se você não pode mudar o bloco, você pode lidar com isso . A razão pela qual um ciclo de retenção é um aviso, não um erro, é que eles não significam necessariamente desgraça para o seu aplicativo. Se MyDataProcessor
for capaz de liberar os blocos quando a operação estiver concluída, antes que seu pai tente liberá-lo, o ciclo será interrompido e tudo será limpo corretamente. Se você pudesse ter certeza disso, a coisa certa a fazer seria usar a #pragma
para suprimir os avisos para esse bloco de código. (Ou use um sinalizador de compilador por arquivo. Mas não desative o aviso para todo o projeto.)
Você também pode usar um truque semelhante acima, declarando uma referência fraca ou sem retenção e usando-a no bloco. Por exemplo:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Todas as três opções acima fornecerão uma referência sem reter o resultado, embora todas se comportem de maneira um pouco diferente: __weak
tentará zerar a referência quando o objeto for liberado; __unsafe_unretained
deixará você com um ponteiro inválido; __block
na verdade, adicionará outro nível de indireção e permitirá que você altere o valor da referência de dentro do bloco (neste caso, irrelevante, pois dp
não é usado em nenhum outro lugar).
O melhor depende do código que você pode alterar e do que não pode. Mas espero que isso tenha lhe dado algumas idéias sobre como proceder.
dp
for liberado (por exemplo, se foi um controlador de exibição e foi populado), a linha[dp.delegate ...
causará EXC_BADACCESS?strong
ouweak
?@weakify(..)
e@strongify(...)
que permite usarself
em bloco de maneira não retida.Há também a opção de suprimir o aviso quando tiver certeza de que o ciclo será interrompido no futuro:
Dessa forma, você não precisa se preocupar com
__weak
,self
aliasing e prefixo ivar explícito.fonte
__weak id weakSelf = self;
comportamento é fundamentalmente diferente do que suprimir o aviso. A pergunta começou com "... se você está certo de que o ciclo de retenção será interrompido"[array addObject:weakObject];
Se o fracoObjeto foi lançado, isso causa uma falha. Claramente, isso não é preferido em relação a um ciclo de retenção. Você precisa entender se o seu bloco realmente dura o tempo suficiente para justificar o enfraquecimento e também se você deseja que a ação no bloco dependa se o objeto fraco ainda é válido.Para uma solução comum, eu os defino no cabeçalho de pré-compilação. Evita a captura e ainda ativa a ajuda do compilador, evitando o uso
id
Então, no código, você pode fazer:
fonte
self
dentro do seu bloco @weakify (self); bloco de identificação = ^ {@strongify (self); [self.delegate myAPIDidFinish: self]; };Acredito que a solução sem o ARC também funcione com o ARC, usando a
__block
palavra-chave:EDIT: De acordo com a transição para as notas de versão do ARC , um objeto declarado com
__block
armazenamento ainda é mantido. Use__weak
(preferencial) ou__unsafe_unretained
(para compatibilidade com versões anteriores).fonte
__block
palavra - chave evitava reter seu referente. Obrigado! Eu atualizei minha resposta monolítica. :-)Combinando algumas outras respostas, é isso que eu uso agora para que um eu fraco digitado use em blocos:
Defino isso como um snippet de código XCode com um prefixo de conclusão de "welf" nos métodos / funções, que ocorre após digitar apenas "nós".
fonte
warning => "capturar a si mesmo dentro do bloco provavelmente conduzirá um ciclo de retenção"
quando você se refere a si mesmo ou a sua propriedade dentro de um bloco que é fortemente retido por si mesmo, como mostra acima o aviso.
por isso, para evitá-lo, temos que fazer uma semana ref
então ao invés de usar
devemos usar
note: o ciclo de retenção geralmente ocorre quando, de alguma maneira, dois objetos se referem um ao outro pelo qual ambos têm contagem de referência = 1 e seu método delloc nunca é chamado.
fonte
A nova maneira de fazer isso é usando @weakify e @strongify marco
Mais informações sobre @Weakify @Strongify Marco
fonte
Se você tiver certeza de que seu código não criará um ciclo de retenção ou que o ciclo será interrompido posteriormente, a maneira mais simples de silenciar o aviso é:
A razão para isso funcionar é que, enquanto o acesso às propriedades por pontos é levado em consideração pela análise do Xcode, e, portanto,
é visto como retido por x de y (no lado esquerdo da atribuição) e por y de x (no lado direito), as chamadas de método não estão sujeitas à mesma análise, mesmo quando são chamadas de método de acesso à propriedade equivalentes ao acesso por pontos, mesmo quando esses métodos de acesso à propriedade são gerados pelo compilador, portanto, em
somente o lado direito é visto como criando uma retenção (por y de x) e nenhum aviso do ciclo de retenção é gerado.
fonte