Sempre passe uma referência fraca de si para o bloco no ARC?


252

Estou um pouco confuso sobre o uso de blocos no Objective-C. Atualmente, uso o ARC e tenho muitos bloqueios no meu aplicativo, atualmente sempre se referindo ao selfinvés de sua referência fraca. Essa pode ser a causa desses blocos reter selfe impedir que sejam desalocados? A questão é: devo sempre usar uma weakreferência de selfem um bloco?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}

Se você quiser um discurso aprofundado sobre esse tópico, leia dhoerl.wordpress.com/2013/04/23/…
David H

Respostas:


721

Ajuda a não se concentrar na parte strongou weakna discussão. Em vez disso, concentre-se na parte do ciclo .

Um ciclo de retenção é um loop que ocorre quando o Objeto A retém o Objeto B e o Objeto B retém o Objeto A. Nessa situação, se um dos objetos for liberado:

  • O Objeto A não será desalocado porque o Objeto B mantém uma referência a ele.
  • Mas o Objeto B nunca será desalocado, desde que o Objeto A tenha uma referência a ele.
  • Mas o Objeto A nunca será desalocado porque o Objeto B mantém uma referência a ele.
  • ao infinito

Portanto, esses dois objetos permanecerão na memória durante toda a vida útil do programa, mesmo que, se tudo estivesse funcionando corretamente, fossem desalocados.

Então, o que nos preocupa é manter ciclos , e não há nada sobre bloqueios que criem esses ciclos. Isso não é um problema, por exemplo:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

O bloco retém self, mas selfnão o retém. Se um ou outro é liberado, nenhum ciclo é criado e tudo é desalocado como deveria.

Onde você entra em problemas é algo como:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

Agora, seu objeto ( self) tem uma strongreferência explícita ao bloco. E o bloco tem uma forte referência implícitaself . Isso é um ciclo, e agora nenhum objeto será desalocado adequadamente.

Como, em uma situação como essa, self por definição já tem uma strongreferência ao bloco, geralmente é mais fácil resolver fazendo uma referência explicitamente fraca selfpara o bloco usar:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

Mas esse não deve ser o padrão padrão que você segue ao lidar com blocos que chamam self! Isso deve ser usado apenas para interromper o que seria um ciclo de retenção entre o eu e o bloco. Se você adotasse esse padrão em qualquer lugar, correria o risco de passar um bloco para algo que foi executado depois que selffoi desalocado.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];

2
Não tenho certeza de que A retenha B, B retenha A fará um ciclo infinito. Da perspectiva da contagem de referência, a contagem de referência de A e B é 1. O que causa um ciclo de retenção para essa situação é quando não há outro grupo com uma referência forte de A e B fora - significa que não podemos alcançar esses dois objetos (nós não pode controlar A para liberar B e vice-versa), portanto, A e B se referem um ao outro para se manterem vivos.
Danyun Liu

@ Danyun Embora seja verdade que um ciclo de retenção entre A e B não é irrecuperável até que todas as outras referências a esses objetos tenham sido liberadas, isso não torna menos um ciclo. Por outro lado, apenas porque um ciclo específico pode ser recuperado não significa que é bom tê-lo em seu código. Ciclos de retenção são um cheiro de mau design.
jemmons

@jemmons Sim, devemos sempre evitar o design do ciclo de retenção, tanto quanto possível.
Danyun Liu

1
@ Mestre É impossível para mim dizer. Depende completamente da implementação do seu -setCompleteionBlockWithSuccess:failure:método. Mas, se paginatorpertencer a ViewController, e esses blocos não ViewControllerforem chamados depois que forem liberados, o uso de uma __weakreferência seria a jogada segura (porque selfpossui o que possui os blocos e, portanto, provavelmente ainda estará presente quando os blocos o chamarem) mesmo que não o mantenham). Mas isso é um monte de "se" s. Realmente depende do que isso deve fazer.
jemmons

1
@ Jai Não, e este é o cerne do problema de gerenciamento de memória com bloqueios / fechamentos. Os objetos são desalocados quando nada os possui. MyObjecte SomeOtherObjectambos são donos do bloco. Mas, como a referência do bloco de volta MyObjecté weak, o bloco não é o proprietário MyObject. Assim, enquanto o bloco é garantido para existir enquanto quer MyObject ou SomeOtherObject existir, não há nenhuma garantia de que MyObjectvai existir enquanto o bloco faz. MyObjectpode ser completamente desalocado e, enquanto SomeOtherObjectainda existir, o bloco ainda estará lá.
21715 jemmons

26

Você não precisa sempre usar uma referência fraca. Se o seu bloco não for retido, mas executado e descartado, você poderá se capturar com força, pois não criará um ciclo de retenção. Em alguns casos, você deseja que o bloco se mantenha até a conclusão do bloco, para que não seja desalocado prematuramente. Se, no entanto, você capturar o bloco fortemente, e dentro do self de captura, ele criará um ciclo de retenção.


Bem, eu apenas executo o bloco como retorno de chamada e não gostaria que fosse desalocado. Mas parece como se eu estivesse criando manter ciclos porque o controlador de vista em questão não se dealloced ...
the_critic

1
Por exemplo, se você tiver um item de botão de barra que é retido pelo controlador de exibição e se capturar fortemente nesse bloco, haverá um ciclo de retenção.
quer

1
@MartinE. Você deve apenas atualizar sua pergunta com exemplos de código. Leo está certo (+1), que não necessariamente causa um ciclo de referência forte, mas pode, dependendo de como você usa esses blocos. Será mais fácil ajudá-lo se você fornecer trechos de código.
Rob

@LeoNatan eu entendo a noção de manter ciclos, mas não estou muito certo o que acontece em blocos, de modo que me confunde um pouco
the_critic

No código acima, sua instância própria será liberada assim que a operação for concluída e o bloco for liberado. Você deve ler como os blocos funcionam, o que e quando eles capturam em seu escopo.
precisa

26

Concordo totalmente com @jemmons:

Mas esse não deve ser o padrão padrão que você segue ao lidar com blocos que se chamam self! Isso deve ser usado apenas para interromper o que seria um ciclo de retenção entre o eu e o bloco. Se você adotasse esse padrão em qualquer lugar, correria o risco de passar um bloco para algo que foi executado depois que o self foi desalocado.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

Para superar esse problema, é possível definir uma forte referência sobre o weakSelfinterior do bloco:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

3
StrongSelf não incrementaria a contagem de referência para fracaSelf? Criando assim um ciclo de retenção?
28415 mskw

12
Os ciclos de retenção são importantes apenas se existirem no estado estático do objeto. Enquanto o código está em execução e seu estado está em fluxo, retenções múltiplas e possivelmente redundantes são boas. De qualquer forma, com relação a esse padrão, capturar uma referência forte não faz nada para o caso de ser desalocado antes do bloco ser executado, isso ainda pode acontecer. Ele garante que o self não seja desalocado durante a execução do bloco. Isso importa se o bloco faz operações assíncronas, dando uma janela para que isso aconteça.
Pierre Houston

@mallduck, sua explicação é ótima. Agora eu entendo isso melhor. Livros não cobriram isso, obrigado.
Huibin Zhang

5
Este não é um bom exemplo de strongSelf, porque a adição explícita de strongSelf é exatamente o que o tempo de execução faria de qualquer maneira: na linha doSomething, uma referência forte é feita durante a chamada de método. Se o fracaSelf já foi invalidado, a referência forte é nula e a chamada do método é não operacional. Onde o strongSelf ajuda é se você tem uma série de operações ou acessa um campo membro ( ->), onde deseja garantir que realmente obteve uma referência válida e a mantém continuamente em todo o conjunto de operações, por exemplo:if ( strongSelf ) { /* several operations */ }
Ethan

19

Como Leo aponta, o código que você adicionou à sua pergunta não sugere um ciclo de referência forte (também conhecido como ciclo de retenção). Um problema relacionado à operação que poderia causar um forte ciclo de referência seria se a operação não estivesse sendo liberada. Embora seu snippet de código sugira que você não definiu sua operação como simultânea, mas, se o tiver, não seria liberada se você nunca publicasse isFinished, ou se tivesse dependências circulares ou algo parecido. E se a operação não for liberada, o controlador de exibição também não será liberado. Sugiro adicionar um ponto de interrupção ou NSLogno deallocmétodo da sua operação e confirmar que está sendo chamado.

Você disse:

Entendo a noção de ciclos de retenção, mas não sei bem o que acontece em blocos, o que me confunde um pouco

Os problemas do ciclo de retenção (ciclo de referência forte) que ocorrem nos blocos são exatamente como os problemas do ciclo de retenção com os quais você está familiarizado. Um bloco manterá referências fortes a todos os objetos que aparecerem dentro do bloco e não liberará essas referências fortes até que o próprio bloco seja liberado. Portanto, se o bloco referenciar self, ou mesmo apenas referenciar uma variável de instância de self, que manterá forte referência a si mesmo, isso não será resolvido até que o bloco seja liberado (ou nesse caso, até que a NSOperationsubclasse seja liberada).

Para obter mais informações, consulte a seção Evitar ciclos de referência fortes ao capturar a si própria do documento Programação com objetivo-C: Trabalhando com blocos .

Se o seu controlador de visualização ainda não estiver sendo liberado, basta identificar onde reside a referência forte não resolvida (supondo que você confirmou que NSOperationestá sendo desalocado). Um exemplo comum é o uso de uma repetição NSTimer. Ou algum delegateobjeto personalizado ou outro que está mantendo uma strongreferência erroneamente . Muitas vezes, você pode usar o Instruments para rastrear onde os objetos estão recebendo suas referências fortes, por exemplo:

registrar contagens de referência no Xcode 6

Ou no Xcode 5:

registrar contagens de referência no Xcode 5


1
Outro exemplo seria se a operação fosse retida no criador do bloco e não liberada após a conclusão. +1 na escrita agradável!
perfil completo de Leo Natan

@LeoNatan concordou, embora o trecho de código o represente como uma variável local que seria liberada se ele estivesse usando o ARC. Mas você está certo!
Rob

1
Sim, eu estava apenas dando um exemplo, como o OP solicitou na outra resposta.
perfil completo de Leo Natan

A propósito, o Xcode 8 possui o "Debug Memory Graph", que é uma maneira ainda mais fácil de encontrar referências fortes a objetos que não foram lançados. Consulte stackoverflow.com/questions/30992338/… .
Rob

0

Algumas explicações ignoram uma condição sobre o ciclo de retenção. [Se um grupo de objetos estiver conectado por um círculo de relacionamentos fortes, eles se manterão vivos, mesmo que não haja referências fortes fora do grupo.] Para obter mais informações, leia o documento


-2

É assim que você pode usar o self dentro do bloco:

// chamada do bloco

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.