capturar-se fortemente neste bloco provavelmente levará a um ciclo de retenção

207

Como posso evitar esse aviso no xcode. Aqui está o trecho de código:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];
user1845209
fonte
timerDispuma propriedade na classe?
Tim
Sim, @property (não atômico, forte) UILabel * timerDisp;
precisa saber é o seguinte
2
O que é isso: player(AVPlayer object)e timerDisp(UILabel)?
22413 Carl Veazey
AVPlayer * player; UILabel * timerDisp;
usar o seguinte comando
5
A verdadeira questão é como silenciar esse aviso sem uma referência fraca desnecessária, quando você souber que a referência circular será quebrada (por exemplo, se você sempre limpar a referência quando uma solicitação de rede terminar).
Glenn Maynard

Respostas:

514

A captura selfaqui está chegando com seu acesso implícito à propriedade self.timerDisp- você não pode consultar selfou propriedades a selfpartir de dentro de um bloco que será fortemente retido porself .

Você pode contornar isso criando uma referência fraca para selfantes de acessar timerDispdentro do seu bloco:

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];
Tim
fonte
13
Tente usar em seu __unsafe_unretainedlugar.
Tim
63
Resolvido. use isto: __unsafe_unretained typeof (self) weakSelf = self; Obrigado pela ajuda @Tim
user1845209
1
Boa resposta, mas levo um pequeno problema com você dizendo: "você não pode se referir a si mesmo ou a propriedades de si mesmo dentro de um bloco que será fortemente retido por si mesmo". Isto não é estritamente verdade. Por favor, veja minha resposta abaixo. Melhor dizer, “você deve tomar muito cuidado se você se referir a si mesmo ...”
Chris Suter
8
Não vejo um ciclo de retenção no código do OP. O bloco não é fortemente retido por self, é retido pela fila de expedição principal. Estou errado?
31413 Erikprice
3
@erikprice: você não está errado. Interpretei a pergunta como sendo principalmente sobre o erro que o Xcode apresenta ("Como posso evitar esse aviso no xcode"), e não sobre a presença real de um ciclo de retenção. Você está certo ao dizer que nenhum ciclo de retenção é evidente apenas no snippet OP fornecido.
Tim
52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

E uma coisa muito importante a lembrar: não use variáveis ​​de instância diretamente no bloco, use-as como propriedades de um objeto fraco, exemplo:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

e não se esqueça de fazer:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

outro problema pode aparecer se você passar uma cópia fraca de não retida por ninguém:

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

se vcToGoserá desalocado e, em seguida, este bloco disparado, acredito que você irá travar com o seletor não reconhecido em uma lixeira que contém vcToGo_variável agora. Tente controlá-lo.

iiFreeman
fonte
3
Essa seria uma resposta mais forte se você também explicasse.
Eric J.
43

Versão melhorada

__strong typeof(self) strongSelf = weakSelf;

Crie uma referência forte para essa versão fraca como a primeira linha do seu bloco. Se o self ainda existir quando o bloco começar a ser executado e não voltar a zero, essa linha garantirá que ele persista durante toda a vida útil do bloco.

Então, a coisa toda seria assim:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

Eu li este artigo várias vezes. Este é um excelente artigo de Erica Sadun sobre Como evitar problemas ao usar blocos e o NSNotificationCenter


Atualização rápida:

Por exemplo, rapidamente, um método simples com bloco de sucesso seria:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

Quando chamamos esse método e precisamos usar selfno bloco de sucesso. Usaremos os recursos [weak self]e guard let.

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Essa dança dita forte-fraca é usada pelo projeto popular de código aberto Alamofire.

Para mais informações, consulte o guia de estilo rápido

Warif Akhand Rishi
fonte
E se você fez typeof(self) strongSelf = self;fora do bloco (em vez de __ fraco) e strongSelf = nil;depois disse após o uso? Não vejo como o seu exemplo garante que o fracoSelf não seja nulo no momento em que o bloco é executado.
Matt
Para evitar possíveis ciclos de retenção, estabelecemos uma auto-referência fraca fora de qualquer bloco que use self em seu código. De uma maneira, você precisa garantir que o bloco seja executado. Outro bloco de seu código agora é responsável por liberar sua memória retida anteriormente.
Warif Akhand Rishi,
@ Matt, o objetivo deste exemplo não é tornar o fracoSelf retido. O objetivo é que, se o self fraco não for nulo, faça uma forte referência dentro do bloco. Assim, uma vez que o bloco começa a ser executado, o eu não se torna nulo dentro do bloco.
Warif Akhand Rishi,
15

Em outra resposta, Tim disse:

você não pode se referir a si mesmo ou a suas propriedades dentro de um bloco que será fortemente retido por si mesmo.

Isso não é bem verdade. Não há problema em você fazer isso desde que interrompa o ciclo em algum momento. Por exemplo, digamos que você tenha um timer que é acionado com um bloco que se retém e também mantém uma referência forte ao timer. Isso é perfeitamente correto se você sempre souber que destruirá o cronômetro em algum momento e interromperá o ciclo.

No meu caso, agora, eu tinha esse aviso de código que fazia:

[x setY:^{ [x doSomething]; }];

Agora eu sei que o clang só produzirá esse aviso se detectar o método iniciado com "set" (e outro caso especial que não vou mencionar aqui). Para mim, eu sei que não há perigo de haver um loop de retenção, então mudei o nome do método para "useY:" É claro que isso pode não ser apropriado em todos os casos e geralmente você deseja usar uma referência fraca, mas Achei interessante notar minha solução, caso ajude outras pessoas.

Chris Suter
fonte
4

Muitas vezes, esse não é realmente um ciclo de retenção .

Se você sabe que não é, não precisa trazer-se fracos infrutíferos ao mundo.

A Apple até obriga esses avisos com a API UIPageViewController, incluindo um método definido (que aciona esses avisos - como mencionado em outro lugar - pensando que você está definindo um valor para um ivar que é um bloco) e um bloco manipulador de conclusão (no qual você sem dúvida se referirá a si mesmo).

Aqui estão algumas diretrizes do compilador para remover o aviso dessa linha de código:

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop
bshirley
fonte
1

Adicionando dois centavos em melhorar a precisão e o estilo. Na maioria dos casos, você usará apenas um ou alguns membros selfdeste bloco, provavelmente apenas para atualizar um controle deslizante. Fundição selfé um exagero. Em vez disso, é melhor ser explícito e converter apenas os objetos que você realmente precisa dentro do bloco. Por exemplo, se for uma instância de UISlider*, digamos, _timeSliderfaça o seguinte antes da declaração do bloco:

UISlider* __weak slider = _timeSlider;

Depois é só usar sliderdentro do bloco. Tecnicamente, isso é mais preciso, pois restringe o ciclo de retenção potencial apenas ao objeto que você precisa, e não a todos os objetos internos self.

Exemplo completo:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

Além disso, provavelmente o objeto que está sendo convertido em um ponteiro fraco já é um ponteiro fraco por dentro self, além de minimizar ou eliminar completamente a probabilidade de um ciclo de retenção. No exemplo acima, _timeSlideré realmente uma propriedade armazenada como uma referência fraca, por exemplo:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

Em termos de estilo de codificação, como em C e C ++, as declarações de variáveis ​​são melhor lidas da direita para a esquerda. Declarando SomeType* __weak variablenesta ordem lê mais naturalmente da direita para a esquerda, como: variable is a weak pointer to SomeType.

Luis Artola
fonte
1

Encontrei este aviso recentemente e queria entendê-lo um pouco melhor. Após algumas tentativas e erros, descobri que ele se origina do início de um método com "add" ou "save". O objetivo C trata os nomes dos métodos que começam com "novo", "alocar" etc. como retornando um objeto retido, mas não menciona (que eu posso encontrar) nada sobre "adicionar" ou "salvar". No entanto, se eu usar um nome de método dessa maneira:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

Vou ver o aviso na linha [self done]. No entanto, isso não irá:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

Vou seguir em frente e usar a maneira "__weak __typeof (self) weakSelf = self" para fazer referência ao meu objeto, mas realmente não gosto de fazê-lo, pois isso confundirá um futuro eu e / ou outro desenvolvedor. Obviamente, eu também não poderia usar "adicionar" (ou "salvar"), mas isso é pior, pois tira o significado do método.

Ray M.
fonte