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.