Gerenciamento de memória Objective C com blocos, ARC e não ARC
Pergunta
Já uso blocos há algum tempo, mas sinto falta de coisas no gerenciamento de memória em ambientes ARC e não ARC.Sinto que uma compreensão mais profunda me fará anular muitos vazamentos de memória.
AFNetworking é meu principal uso de Blocks em uma aplicação específica.Na maioria das vezes, dentro de um manipulador de conclusão de uma operação, faço algo como "[self.myArray addObject]".
Em ambientes habilitados para ARC e não ARC, "self" será retido de acordo com este artigo da Apple.
Isso significa que sempre que um bloco de conclusão de uma operação de rede AFNetworking é chamado, self é retido dentro desse bloco e liberado quando esse bloco sai do escopo.Acredito que isto se aplica tanto à ARC como aos não-ARC.Executei a ferramenta Leaks e o Static Analyzer para encontrar vazamentos de memória.Nenhum mostrou nada.
No entanto, só recentemente me deparei com um aviso que não consegui entender.Estou usando ARC neste exemplo específico.
Tenho duas variáveis de instância que indicam a conclusão e a falha de uma operação de rede
@property (nonatomic, readwrite, copy) SFCompletionBlock completionBlock;
@property (nonatomic, readwrite, copy) SFFailureBlock failureBlock;
@synthesize failureBlock = _failureBlock;
@synthesize operation = _operation;
Em algum lugar do código, eu faço isso:
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id
responseObject) {
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
_failureBlock(error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"nothing");
}];
O Xcode reclama da linha que chama o failedBlock, com a mensagem "Capturar" self "fortemente neste bloco provavelmente resultará em um ciclo de retenção.Acredito que o Xcode está certo:o bloco de falha retém self e self mantém sua própria cópia do bloco, portanto, nenhum dos dois será desalocado.
No entanto, tenho as seguintes perguntas/observações.
1) Se eu alterar _failureBlock(error) para "self.failureBlock(error)" (sem aspas), o compilador para de reclamar.Por que é que?Isso é um vazamento de memória que o compilador não percebe?
2) Em geral, qual é a melhor prática para trabalhar com blocos em ambientes ARC e não habilitados para ARC ao usar blocos que são variáveis de instância?Parece que no caso de blocos de conclusão e falha no AFNetworking, esses dois blocos são não variáveis de instância, então elas provavelmente não se enquadram na categoria de ciclos de retenção que descrevi acima.Mas ao usar blocos de progresso no AFNetworking, o que pode ser feito para evitar ciclos de retenção como o acima?
Eu adoraria ouvir a opinião de outras pessoas sobre ARC e não-ARC com blocos e problemas/soluções com gerenciamento de memória.Considero essas situações propensas a erros e sinto que é necessária alguma discussão sobre isso para esclarecer as coisas.
Não sei se isso importa, mas uso o Xcode 4.4 com o LLVM mais recente.
Solução
1) Se eu alterar _FailureBlock (erro) para "self.FailureBlock (erro)" (sem cotações), o compilador para de reclamar.Por que é que?Isso é um vazamento de memória que o compilador erra?
O ciclo de retenção existe em ambos os casos.Se você tem como alvo o iOS 5+, pode passar uma referência fraca a si mesmo:
__weak MyClass *weakSelf;
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
if (weakSelf.failureBlock) weakSelf.failureBlock(error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"nothing");
}];
Agora self não será retido e, se for desalocado antes que o retorno de chamada seja invocado, o retorno de chamada será autônomo.No entanto, é possível que ele esteja passando por desalocação enquanto o retorno de chamada é invocado em um thread em segundo plano, portanto, esse padrão pode criar falhas ocasionais.
2) Em geral, qual é a melhor prática para trabalhar com blocos nos ambientes de ARC e não-ARC ao usar blocos que são variáveis de instância?Parece que, no caso de blocos de conclusão e falha no trabalho de afnet, esses dois blocos não são variáveis de instância, para que provavelmente não se enquadram na categoria de ciclos de retenção que descrevi acima.Mas, ao usar o progresso dos blocos para o Afnetworking, o que pode ser feito para evitar reter ciclos como o acima?
Na maioria das vezes, eu acho é melhor não armazenar blocos em variáveis de instância.Se, em vez disso, você retornar o bloco de um método em sua classe, ainda terá um ciclo de retenção, mas ele só existe desde o momento em que o método é invocado até o momento em que o bloco é liberado.Portanto, evitará que sua instância seja desalocada durante a execução do bloco, mas o ciclo de retenção termina quando o bloco é liberado:
-(SFCompletionBlock)completionBlock {
return ^(AFHTTPRequestOperation *operation , id responseObject ) {
[self doSomethingWithOperation:operation];
};
}
[self.operation setCompletionBlockWithSuccess:[self completionBlock]
failure:[self failureBlock]
];
Outras dicas
Isso significa que, sempre que um bloco de conclusão de uma operação de rede Afnetworking é chamado, o Self é retido dentro desse bloco e liberado quando esse bloco sai do escopo.
Não, self
é retido pelo bloco quando o bloco é criado.E é liberado quando o bloco é desalocado.
Acredito que o Xcode está certo:O bloco de falha se mantém e mantém sua própria cópia do bloco, para que nenhum dos dois seja desalocado.
O bloco em questão que retém self
é o bloco de conclusão passado para setCompletionBlockWithSuccess
. self
não contém uma referência a este bloco.Em vez de, self.operation
(provavelmente algum tipo de NSOperation
) retém os blocos enquanto está em execução.Portanto, há temporariamente um ciclo.Porém, quando a operação terminar, o ciclo será interrompido.
1) Se eu alterar _FailureBlock (erro) para "self.FailureBlock (erro)" (sem cotações), o compilador para de reclamar.Por que é que?Isso é um vazamento de memória que o compilador erra?
Não deveria haver diferença. self
é capturado em ambos os casos.Não é garantido que o compilador capture todos os casos de ciclos de retenção.