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.

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top