catturare fortemente se stessi in questo blocco probabilmente porterà a un ciclo di mantenimento


207

Come posso evitare questo avviso in xcode. Ecco il frammento di codice:

[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
}];

È timerDispuna proprietà sulla classe?
Tim

Sì, @property (nonatomic, strong) UILabel * timerDisp;
user1845209,

2
Di cosa si tratta: player(AVPlayer object)e timerDisp(UILabel)?
Carl Veazey,

Lettore AVPlayer *; UILabel * timerDisp;
user1845209

5
La vera domanda è come silenziare questo avviso senza inutili riferimenti deboli su se stessi, quando si conosce che il riferimento circolare verrà interrotto (ad es. Se si cancella sempre il riferimento al termine di una richiesta di rete).
Glenn Maynard,

Risposte:


514

La cattura di selfqui sta arrivando con il tuo accesso alle proprietà implicite di self.timerDisp- non puoi fare riferimento selfo proprietà selfda all'interno di un blocco che sarà fortemente trattenuto da self.

Puoi aggirare questo creando un riferimento debole a selfprima di accedere timerDispall'interno del tuo blocco:

__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]];
                                            }];

13
Prova ad usare __unsafe_unretainedinvece.
Tim

63
Risolto. usare questo invece: __unsafe_unretained typeof (self) weakSelf = self; grazie per l'aiuto
@Tim

1
Buona risposta, ma prendo un piccolo problema con te che dici: "non puoi fare riferimento a te stesso o alle proprietà di te stesso all'interno di un blocco che sarà fortemente trattenuto da te stesso". Questo non è strettamente vero. Si prega di vedere la mia risposta qui sotto. Meglio dire: " devi stare molto attento se ti riferisci a te stesso ..."
Chris Suter,

8
Non vedo un ciclo di mantenimento nel codice del PO. Il blocco non è fortemente mantenuto da self, è mantenuto dalla coda di spedizione principale. Ho sbagliato?
erikprice,

3
@erikprice: non sbagli. Ho interpretato la domanda principalmente sull'errore presentato da Xcode ("Come posso evitare questo avviso in xcode"), piuttosto che sulla presenza effettiva di un ciclo di mantenimento. Hai ragione nel dire che nessun ciclo di mantenimento è evidente solo dallo snippet OP fornito.
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 una cosa molto importante da ricordare: non usare le variabili di istanza direttamente nel blocco, usarlo come proprietà di un oggetto debole, esempio:

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 non dimenticare di fare:

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

un altro problema può apparire se passerai copia debole di non trattenuto da nessuno oggetto:

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

se vcToGoverrà deallocato e quindi questo blocco attivato, credo che otterrai un arresto anomalo con un selettore non riconosciuto su un cestino che vcToGo_ora contiene una variabile. Prova a controllarlo.


3
Questa sarebbe una risposta più forte se la spiegassi anche tu.
Eric J.

43

Versione migliore

__strong typeof(self) strongSelf = weakSelf;

Crea un forte riferimento a quella versione debole come prima riga del tuo blocco. Se il sé esiste ancora quando il blocco inizia a essere eseguito e non è tornato a zero, questa linea garantisce che persista per tutta la durata dell'esecuzione del blocco.

Quindi l'intera cosa sarebbe così:

// 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
    }
}];

Ho letto questo articolo molte volte. Questo è un eccellente articolo di Erica Sadun su Come evitare problemi quando si usano blocchi e NSNotificationCenter


Aggiornamento rapido:

Ad esempio, in breve tempo un metodo semplice con blocco di successo sarebbe:

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

Quando chiamiamo questo metodo e dobbiamo usarlo selfnel blocco di successo. Useremo le funzionalità [weak self]e guard let.

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

Questa cosiddetta danza forte-debole viene utilizzata dal popolare progetto open source Alamofire.

Per maggiori informazioni, consulta la guida rapida


E se lo facessi typeof(self) strongSelf = self;al di fuori del blocco (invece di __weak), nel blocco detto strongSelf = nil;dopo l'uso? Non vedo come il tuo esempio assicuri che weakSelf non sia nullo al momento dell'esecuzione del blocco.
Matt,

Per evitare possibili cicli di mantenimento, stabiliamo un debole auto-riferimento al di fuori di qualsiasi blocco che usa self nel suo codice. In ogni caso, devi assicurarti che il blocco sia eseguito. Un altro blocco di codice ur è ora responsabile della liberazione della memoria precedentemente trattenuta.
Warif Akhand Rishi,

@Matt lo scopo di questo esempio non è di mantenere il punto debole. Lo scopo è, se il debole Sé non è zero, fare un forte riferimento all'interno del blocco. Quindi una volta che il blocco inizia l'esecuzione con sé, il sé non diventa zero all'interno del blocco.
Warif Akhand Rishi,

15

In un'altra risposta, Tim ha detto:

non puoi fare riferimento a sé o proprietà su di sé all'interno di un blocco che sarà fortemente trattenuto da sé.

Questo non è del tutto vero. Va bene per te farlo fintanto che a un certo punto interrompi il ciclo. Ad esempio, supponiamo che tu abbia un timer che si attiva con un blocco che trattiene se stesso e che conservi anche un forte riferimento al timer. Questo va perfettamente bene se sai sempre che a un certo punto distruggerai il timer e interromperai il ciclo.

Nel mio caso proprio ora, avevo questo avviso per il codice che faceva:

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

Ora capisco che clang produrrà questo avviso solo se rileva che il metodo inizia con "set" (e un altro caso speciale che non menzionerò qui). Per me, so che non c'è pericolo che ci sia un ciclo di mantenimento, quindi ho cambiato il nome del metodo in "useY:" Naturalmente, ciò potrebbe non essere appropriato in tutti i casi e di solito si vorrà usare un riferimento debole, ma Ho pensato che valesse la pena notare la mia soluzione nel caso in cui aiuti gli altri.


4

Molte volte, questo non è in realtà un ciclo di mantenimento .

Se sai che non lo è, non è necessario portare se stessi inutili nel mondo.

Apple ci impone persino questi avvisi con l'API al loro UIPageViewController, che include un metodo set (che attiva questi avvisi - come menzionato altrove - pensando che stai impostando un valore su un ivar che è un blocco) e un blocco gestore di completamento (in cui ti riferirai senza dubbio a te stesso).

Ecco alcune direttive del compilatore per rimuovere l'avviso da quella riga di codice:

#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

1

Aggiungendo due centesimi per migliorare la precisione e lo stile. Nella maggior parte dei casi utilizzerai solo uno o un paio di membri di selfquesto blocco, molto probabilmente solo per aggiornare un dispositivo di scorrimento. Il casting selfè eccessivo. Invece, è meglio essere espliciti e lanciare solo gli oggetti di cui hai veramente bisogno all'interno del blocco. Ad esempio, se si tratta di un'istanza di UISlider*, diciamo _timeSlider, basta fare quanto segue prima della dichiarazione di blocco:

UISlider* __weak slider = _timeSlider;

Quindi basta usare sliderall'interno del blocco. Tecnicamente questo è più preciso in quanto restringe il potenziale ciclo di mantenimento solo all'oggetto di cui hai bisogno, non a tutti gli oggetti all'interno self.

Esempio completo:

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

Inoltre, molto probabilmente l'oggetto che viene lanciato su un puntatore debole è già un puntatore debole all'interno self, minimizzando o eliminando completamente la probabilità di un ciclo di mantenimento. Nell'esempio sopra, in _timeSliderrealtà è una proprietà memorizzata come riferimento debole, ad esempio:

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

In termini di stile di codifica, come per C e C ++, le dichiarazioni delle variabili vengono lette meglio da destra a sinistra. Dichiarare SomeType* __weak variablein questo ordine si legge più naturalmente da destra a sinistra come: variable is a weak pointer to SomeType.


1

Ho incontrato questo avviso di recente e volevo capirlo un po 'meglio. Dopo un po 'di tentativi ed errori, ho scoperto che ha origine da un metodo che inizia con "aggiungi" o "salva". L'obiettivo C considera i nomi dei metodi che iniziano con "nuovo", "alloc", ecc. Come restituzione di un oggetto conservato ma non menziona (che posso trovare) nulla su "aggiungi" o "salva". Tuttavia, se utilizzo un nome metodo in questo modo:

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

Vedrò l'avvertimento sulla riga [self made]. Tuttavia, ciò non:

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

Continuerò e userò il modo "__weak __typeof (self) weakSelf = self" per fare riferimento al mio oggetto, ma in realtà non mi piace doverlo fare poiché confonderà un futuro me e / o altri sviluppatori. Certo, non potrei anche usare "add" (o "save") ma è peggio perché toglie il significato del metodo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.