Reter o ciclo no `self 'com blocos


167

Receio que essa pergunta seja bastante básica, mas acho relevante para muitos programadores de Objective-C que estão entrando em blocos.

O que ouvi é que, como os blocos capturam variáveis ​​locais referenciadas como constcópias, o uso selfdentro de um bloco pode resultar em um ciclo de retenção, caso esse bloco seja copiado. Portanto, devemos usar __blockpara forçar o bloco a lidar diretamente, em selfvez de copiá-lo.

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

em vez de apenas

[someObject messageWithBlock:^{ [self doSomething]; }];

O que eu gostaria de saber é o seguinte: se isso for verdade, existe uma maneira de evitar a feiura (além de usar o GC)?


3
Eu gosto de chamar meus selfproxies thisapenas para mudar as coisas. Em JavaScript, chamo meus thisencerramentos self, para que pareça agradável e equilibrado. :)
devios1

Pergunto-me há qualquer ação equivalente precisa ser feito se eu estou usando blocos Swift
Ben Lu

@BenLu absolutamente! nos fechamentos Swift (e as funções passadas em volta dessa menção de forma implícita ou explícita) se manterão. Às vezes, isso é desejado e, outras vezes, cria um ciclo (porque o fechamento em si é de propriedade de si mesmo (ou pertence a algo que ele próprio possui). A principal razão pela qual isso acontece é por causa do ARC.
Ethan

1
Para evitar problemas, a maneira apropriada de definir 'self' a ser usado em um bloco é '__typeof (self) __weak fracoSelf = self;' para ter uma referência fraca.
XLE_22

Respostas:


169

Estritamente falando, o fato de ser uma cópia const não tem nada a ver com esse problema. Os blocos reterão quaisquer valores obj-c capturados quando criados. Acontece que a solução alternativa para o problema de cópia constante é idêntica à solução alternativa para o problema de retenção; ou seja, usando o__block classe de armazenamento para a variável

De qualquer forma, para responder sua pergunta, não há alternativa real aqui. Se você estiver projetando sua própria API baseada em bloco e fizer sentido, poderá fazer com que o bloco passe o valor de selfin como argumento. Infelizmente, isso não faz sentido para a maioria das APIs.

Observe que a referência a um ivar tem exatamente o mesmo problema. Se você precisar fazer referência a um ivar no seu bloco, use uma propriedade ou use bself->ivar.


Adendo: Ao compilar como ARC, as __blockquebras não retêm mais ciclos. Se você estiver compilando para o ARC, precisará usar __weakou em __unsafe_unretainedvez disso.


Sem problemas! Se isso respondeu à sua pergunta de forma satisfatória, eu apreciaria se você pudesse escolher isso como a resposta correta para sua pergunta. Caso contrário, informe-me como posso responder melhor à sua pergunta.
Lily Ballard

4
Sem problemas, Kevin. O SO atrasa você de selecionar uma resposta para uma pergunta imediatamente, então tive que voltar um pouco mais tarde. Felicidades.
Jonathan Sterling

__unsafe_unretained id bself = self;
Caleb

4
@JKLaiho: Claro, também __weakestá bem. Se você sabe que o objeto não pode estar fora do escopo quando o bloco é chamado, __unsafe_unretainedé sempre um pouco mais rápido, mas em geral isso não fará diferença. Se você o usar __weak, certifique-se de jogá-lo em uma __strongvariável local e teste-o nilantes de fazer qualquer coisa com ele.
Lily Ballard

2
@Rpranata: Sim. __blockO efeito colateral de não reter e liberar se deveu simplesmente à incapacidade de raciocinar adequadamente sobre isso. Com o ARC, o compilador ganhou essa capacidade e __blockagora retém e libera. Se você precisar evitar isso, precisará usar o __unsafe_unretainedque instrui o compilador a não executar retenções ou liberações no valor da variável.
Lily Ballard

66

Apenas use:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

Para mais informações: WWDC 2011 - Blocos e expedição da Grand Central na prática .

https://developer.apple.com/videos/wwdc/2011/?id=308

Nota: se isso não funcionar, você pode tentar

__weak typeof(self)weakSelf = self;

2
E por acaso você o encontrou :)?
precisa saber é

2
Você pode conferir o vídeo aqui - developer.apple.com/videos/wwdc/2011/…
nanjunda

Você é capaz de fazer referência a si mesmo em "someOtherMethod"? A auto-referência se enfraqueceria nesse ponto ou isso também criaria um ciclo de retenção?
Oren

Oi @ Oren, se você tentar fazer referência a si mesmo dentro de "someOtherMethod", receberá um aviso do Xcode. Minha abordagem apenas faz uma referência fraca a si próprio.
3lvis

1
Eu só recebi um aviso ao me referir diretamente dentro do bloco. Colocar o self dentro de someOtherMethod não causou nenhum aviso. Isso ocorre porque o xcode não é inteligente o suficiente ou não é um problema? Referenciar a si mesmo em someOtherMethod já se refere a weakSelf já que é nisso que está chamando o método?
Oren

22

Isso pode ser óbvio, mas você só precisa fazer o selfalias feio quando sabe que terá um ciclo de retenção. Se o bloqueio é apenas uma coisa, acho que você pode ignorar com segurança a retenção self. O caso ruim é quando você tem o bloco como uma interface de retorno de chamada, por exemplo. Como aqui:

typedef void (^BufferCallback)(FullBuffer* buffer);

@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end

@implementation AudioProcessor

- (id) init {
    
    [self setBufferCallback:^(FullBuffer* buffer) {
        [self whatever];
    }];
    
}

Aqui, a API não faz muito sentido, mas faria sentido ao se comunicar com uma superclasse, por exemplo. Nós retemos o manipulador de buffer, o manipulador de buffer nos mantém. Compare com algo como isto:

typedef void (^Callback)(void);

@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end

@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end

@implementation Foo
- (void) somewhere {
    [encoder encodeVideoAndCall:^{
        [self doSomething];
    }];
}

Nessas situações, eu não faço o selfalias. Você obtém um ciclo de retenção, mas a operação tem vida curta e o bloco fica sem memória eventualmente, interrompendo o ciclo. Mas minha experiência com blocos é muito pequena e pode ser que o selfaliasing seja uma prática recomendada a longo prazo.


6
Bom ponto. É apenas um ciclo de retenção se o eu estiver mantendo o bloco vivo. No caso de blocos que nunca são copiados ou de duração limitada garantida (por exemplo, o bloco de conclusão de uma animação UIView), você não precisa se preocupar com isso.
Lily Ballard

6
Em princípio, você está correto. No entanto, se você executasse o código no exemplo, falharia. As propriedades do bloco sempre devem ser declaradas como copy, não retain. Se eles são justos retain, então não há garantia de que eles serão removidos da pilha, o que significa que, quando você for executá-lo, ele não estará mais lá. (e copiar e bloco já copiado é otimizado para a reter)
Dave DeLong

Ah, claro, um erro de digitação. Passei pela retainfase há um tempo e rapidamente percebi o que você está dizendo :) Obrigado!
zoul

Eu tenho certeza que retainé completamente ignorado por blocos (a menos que eles já tenham saído da pilha copy).
9407 Steven Fisher

@ Dave DeLong, Não, não falharia, já que a propriedade (reter) é usada apenas para uma referência a objeto, não para o bloco. Não é necessário que uma cópia seja usada aqui ...
DeniziOS

20

Postando outra resposta, porque isso também foi um problema para mim. Originalmente, pensei que tinha que usar blockSelf em qualquer lugar onde houvesse uma auto-referência dentro de um bloco. Não é esse o caso, é apenas quando o próprio objeto possui um bloco. E, de fato, se você usar o blockSelf nesses casos, o objeto poderá ser desalocado antes que você obtenha o resultado de volta do bloco e depois travará quando tentar chamá-lo, tão claramente você deseja que seja retido até a resposta volta.

O primeiro caso demonstra quando um ciclo de retenção ocorrerá porque contém um bloco que é referenciado no bloco:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface ContainsBlock : NSObject 

@property (nonatomic, copy) MyBlock block;

- (void)callblock;

@end 

@implementation ContainsBlock
@synthesize block = _block;

- (id)init {
    if ((self = [super init])) {

        //__block ContainsBlock *blockSelf = self; // to fix use this.
        self.block = ^{
                NSLog(@"object is %@", self); // self retain cycle
            };
    }
    return self;
}

- (void)dealloc {
    self.block = nil;
    NSLog (@"ContainsBlock"); // never called.
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 

@end 

 int main() {
    ContainsBlock *leaks = [[ContainsBlock alloc] init];
    [leaks callblock];
    [leaks release];
}

Você não precisa blockSelf no segundo caso, porque o objeto de chamada não possui um bloco que causará um ciclo de retenção quando você fizer referência a si próprio:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface BlockCallingObject : NSObject 
@property (copy, nonatomic) MyBlock block;
@end

@implementation BlockCallingObject 
@synthesize block = _block;

- (void)dealloc {
    self.block = nil;
    NSLog(@"BlockCallingObject dealloc");
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 
@end

@interface ObjectCallingBlockCallingObject : NSObject 
@end

@implementation ObjectCallingBlockCallingObject 

- (void)doneblock {
    NSLog(@"block call complete");
}

- (void)dealloc {
    NSLog(@"ObjectCallingBlockCallingObject dealloc");
    [super dealloc];
} 

- (id)init {
    if ((self = [super init])) {

        BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
        myobj.block = ^() {
            [self doneblock]; // block in different object than this object, no retain cycle
        };
        [myobj callblock];
        [myobj release];
    }
    return self;
}
@end

int main() {

    ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
    [myObj release];

    return 0;
} 

Esse é um equívoco comum e pode ser perigoso, porque os blocos que devem ser retidos selfpodem não ser devidos às pessoas que aplicam essa correção em excesso. Este é um bom exemplo de como evitar ciclos de retenção em código não-ARC, obrigado pela postagem.
CarlVeazey

9

Lembre-se também de que podem ocorrer ciclos de retenção se o seu bloco se referir a outro objeto que então retém self.

Não tenho certeza de que a coleta de lixo possa ajudar nesses ciclos de retenção. Se o objeto que retém o bloco (que chamarei de objeto servidor) sobrevive self(o objeto cliente), a referência aself dentro do bloco não será considerada cíclica até que o próprio objeto retentor seja liberado. Se o objeto do servidor sobreviver a seus clientes, você pode ter um vazamento de memória significativo.

Como não há soluções limpas, eu recomendaria as seguintes soluções alternativas. Sinta-se à vontade para escolher um ou mais deles para corrigir seu problema.

  • Use blocos apenas para conclusão , e não para eventos abertos. Por exemplo, use blocos para métodos como doSomethingAndWhenDoneExecuteThisBlock:, e não métodos comosetNotificationHandlerBlock: . Os blocos usados ​​para conclusão têm fins de vida definidos e devem ser liberados pelos objetos do servidor após serem avaliados. Isso impede que o ciclo de retenção viva por muito tempo, mesmo que ocorra.
  • Faça aquela dança de referência fraca que você descreveu.
  • Forneça um método para limpar seu objeto antes que ele seja lançado, que "desconecta" o objeto dos objetos do servidor que podem conter referências a ele; e chame esse método antes de chamar release no objeto. Embora esse método seja perfeitamente adequado se seu objeto tiver apenas um cliente (ou for um singleton em algum contexto), mas será quebrado se tiver vários clientes. Você está basicamente derrotando o mecanismo de contagem de retenção aqui; isso é semelhante a ligar em deallocvez de release.

Se você estiver escrevendo um objeto de servidor, use argumentos de bloco apenas para conclusão. Não aceite argumentos de bloco para retornos de chamada, como setEventHandlerBlock:. Em vez disso, volte ao padrão clássico de delegação: crie um protocolo formal e anuncie um setEventDelegate:método. Não retenha o delegado. Se você nem deseja criar um protocolo formal, aceite um seletor como retorno de chamada delegado.

E, por último, esse padrão deve tocar alarmes:

- (nulo) dealloc {
    [myServerObject releaseCallbackBlocksForObject: self];
    ...
}

Se você está tentando desengatar os blocos que podem se referir selfde dentro dealloc, já está com problemas. deallocnunca pode ser chamado devido ao ciclo de retenção causado pelas referências no bloco, o que significa que seu objeto simplesmente vazará até que o objeto do servidor seja desalocado.


O GC ajuda se você usar __weakadequadamente.
tc.

O rastreamento da coleta de lixo pode, é claro, lidar com os ciclos de retenção. Manter ciclos é apenas um problema para ambientes de contagem de referência
newacct

Como todo mundo sabe, a coleta de lixo foi preterida no OS X v10.8 em favor da ARC (contagem de referência automática) e está programada para ser removida em uma versão futura do OS X ( developer.apple.com/library/mac/#releasenotes / ObjectiveC /… ).
Ricardo Sanchez-Saez

1

__block __unsafe_unretainedmodificadores sugeridos na postagem de Kevin podem causar a exceção de acesso incorreto no caso de bloco executado em um thread diferente. É melhor usar apenas o modificador __block para a variável temp e torná-lo nulo após o uso.

__block SomeType* this = self;
[someObject messageWithBlock:^{
  [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
                      //  multithreading and self was already released
  this = nil;
}];

Não seria realmente mais seguro usar apenas __weak em vez de __block para evitar a necessidade de anular a variável depois de usá-la? Quero dizer, essa solução é ótima se você deseja interromper outros tipos de ciclos, mas certamente não vejo nenhuma vantagem em particular para o "auto" manter ciclos nela.
ale0xB

Você não pode usar __weak se o destino da sua plataforma for o iOS 4.x. Às vezes, também é necessário que o código no bloco tenha sido executado para o objeto válido, não para zero.
precisa saber é o seguinte

1

Você pode usar a biblioteca libextobjc. É bastante popular, é usado no ReactiveCocoa, por exemplo. https://github.com/jspahrsummers/libextobjc

Ele fornece 2 macros @weakify e @strongify, para que você possa ter:

@weakify(self)
[someObject messageWithBlock:^{
   @strongify(self)
   [self doSomething]; 
}];

Isso evita uma referência direta forte, para que não entremos em um ciclo de retenção para si próprio. E também evita que o eu se torne nulo no meio do caminho, mas ainda diminui adequadamente a contagem de retenção. Mais neste link: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
Antes de mostrar o código simplificado, seria melhor saber o que está por trás dele, todos deveriam conhecer as duas linhas reais de código.
Alex Cio

0

Que tal agora?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

Não recebo mais o aviso do compilador.


-1

Bloco: um ciclo de retenção ocorrerá porque contém um bloco que é referenciado no bloco; Se você copiar o bloco e usar uma variável de membro, o self será retido.

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.