Passare sempre il riferimento debole di sé nel blocco in ARC?


252

Sono un po 'confuso sull'uso dei blocchi in Objective-C. Attualmente uso ARC e ho un sacco di blocchi nella mia app, al momento mi riferisco sempre selfinvece al suo debole riferimento. Potrebbe essere questa la causa di questi blocchi che trattengono selfe impediscono che venga distribuito? La domanda è: dovrei sempre usare un weakriferimento di selfin un blocco?

-(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 vuoi un discorso approfondito su questo argomento leggi dhoerl.wordpress.com/2013/04/23/…
David H

Risposte:


721

Aiuta a non concentrarsi sulla parte strongo weaksulla discussione. Concentrati invece sulla parte del ciclo .

Un ciclo di mantenimento è un ciclo che si verifica quando l'oggetto A conserva l'oggetto B e l' oggetto B conserva l'oggetto A. In quella situazione, se uno degli oggetti viene rilasciato:

  • L'oggetto A non verrà deallocato perché l'oggetto B contiene un riferimento ad esso.
  • Ma l'oggetto B non sarà mai deallocato fintanto che l'oggetto A avrà un riferimento ad esso.
  • Ma l'oggetto A non verrà mai deallocato perché l'oggetto B contiene un riferimento ad esso.
  • verso l'infinito

Pertanto, questi due oggetti rimarranno solo nella memoria per tutta la durata del programma, anche se dovrebbero, se tutto funzionasse correttamente, essere deallocato.

Quindi, ciò di cui siamo preoccupati sono i cicli di mantenimento , e non c'è nulla sui blocchi in sé e per sé che creano questi cicli. Questo non è un problema, ad esempio:

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

Il blocco mantiene self, ma selfnon mantiene il blocco. Se l'uno o l'altro viene rilasciato, non viene creato alcun ciclo e tutto viene deallocato come dovrebbe.

Dove ti trovi nei guai è qualcosa di simile:

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

Ora, il tuo oggetto ( self) ha un strongriferimento esplicito al blocco. E il blocco ha un forte riferimento implicito a self. Questo è un ciclo, e ora nessuno dei due oggetti verrà deallocato correttamente.

Perché, in una situazione come questa, self per definizione ha già un strongriferimento al blocco, di solito è più facile risolverlo facendo un riferimento esplicitamente debole selfper il blocco da usare:

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

Ma questo non dovrebbe essere lo schema predefinito che segui quando hai a che fare con blocchi che chiamano self! Questo dovrebbe essere usato solo per interrompere quello che altrimenti sarebbe un ciclo di mantenimento tra sé e il blocco. Se dovessi adottare questo modello ovunque, rischieresti di passare un blocco a qualcosa che è stato eseguito dopo che è selfstato deallocato.

//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
Non sono sicuro che A trattieni B, B trattieni A farà un ciclo infinito. Dal punto di vista del conteggio dei riferimenti, il conteggio dei riferimenti di A e B è 1. La causa di un ciclo di mantenimento per questa situazione è quando non ci sono altri gruppi con un forte riferimento di A e B all'esterno - significa che non possiamo raggiungere questi due oggetti (noi non può controllare A per rilasciare B e viceversa), per cui A e B si riferiscono a vicenda per mantenersi entrambi in vita.
Danyun Liu,

@Danyun Mentre è vero che un ciclo di mantenimento tra A e B non è irrecuperabile fino a quando tutti gli altri riferimenti a questi oggetti non sono stati rilasciati, ciò non lo rende un ciclo. Al contrario, solo perché un determinato ciclo potrebbe essere recuperabile non significa che sia ok averlo nel tuo codice. I cicli di mantenimento sono un odore di cattivo design.
jemmons,

@jemmons Sì, dovremmo sempre evitare il più possibile un progetto di ciclo di mantenimento.
Danyun Liu,

1
@Master È impossibile per me dirlo. Dipende completamente dall'implementazione del tuo -setCompleteionBlockWithSuccess:failure:metodo. Ma se paginatorè di proprietà di ViewControllerquesti blocchi e non vengono chiamati dopo che ViewControllerverrebbero rilasciati, usare un __weakriferimento sarebbe la mossa sicura (perché selfpossiede la cosa che possiede i blocchi, e quindi è probabile che sia ancora in giro quando i blocchi lo chiamano anche se non lo trattengono). Ma sono molti "se". Dipende davvero da cosa dovrebbe fare.
jemmons,

1
@Jai No, e questo è al centro del problema di gestione della memoria con blocchi / chiusure. Gli oggetti vengono deallocati quando nulla li possiede. MyObjected SomeOtherObjectentrambi possiedono il blocco. Ma a causa di nuovo riferimento del blocco MyObjectè weak, il blocco non lo fa proprio MyObject. Così, mentre il blocco è garantito ad esistere finché sia MyObject o SomeOtherObject esiste, non c'è alcuna garanzia che MyObjectesisterà fino a quando il blocco fa. MyObjectpuò essere completamente deallocato e, finché SomeOtherObjectesiste ancora, il blocco sarà ancora lì.
jemmons,

26

Non è necessario utilizzare sempre un riferimento debole. Se il tuo blocco non viene mantenuto, ma eseguito e quindi scartato, puoi catturare fortemente te stesso, poiché non creerà un ciclo di mantenimento. In alcuni casi, si desidera persino che il blocco mantenga il sé fino al completamento del blocco in modo che non si sposti prematuramente. Se, tuttavia, catturi fortemente il blocco e, all'interno di te stesso acquisisci, creerà un ciclo di mantenimento.


Beh, eseguo solo il blocco come callback e non vorrei affatto che fosse auto-distribuito. Ma sembra che stavo creando cicli di mantenimento perché il View Controller in questione non viene distribuito ...
the_critic

1
Ad esempio, se si dispone di un elemento del pulsante della barra che viene mantenuto dal controller di visualizzazione e si acquisisce fortemente se stesso in quel blocco, verrà eseguito un ciclo di conservazione.
Leo Natan,

1
@MartinE. Dovresti semplicemente aggiornare la tua domanda con esempi di codice. Leo ha ragione (+1), che non causa necessariamente un forte ciclo di riferimento, ma può, a seconda di come usi questi blocchi. Sarà più facile per noi aiutarti se fornisci frammenti di codice.
Rob,

@LeoNatan Capisco l'idea dei cicli di mantenimento, ma non sono del tutto sicuro di cosa accada in blocchi, quindi questo mi confonde un po '
the_critic

Nel codice sopra, l'istanza personale verrà rilasciata al termine dell'operazione e al rilascio del blocco. Dovresti leggere su come funzionano i blocchi e cosa e quando catturano nel loro ambito.
Leo Natan,

26

Sono totalmente d'accordo con @jemmons:

Ma questo non dovrebbe essere lo schema predefinito che segui quando hai a che fare con blocchi che chiamano sé! Questo dovrebbe essere usato solo per interrompere quello che altrimenti sarebbe un ciclo di mantenimento tra sé e il blocco. Se dovessi adottare questo modello ovunque, rischieresti di passare un blocco a qualcosa che è stato eseguito dopo che il sé è stato deallocato.

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

Per ovviare a questo problema si può definire un forte riferimento weakSelfall'interno del blocco:

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

3
StrongSelf non aumenterebbe il conteggio dei riferimenti per weakSelf? Creando così un ciclo di mantenimento?
mskw,

12
I cicli di mantenimento sono importanti solo se esistono nello stato statico dell'oggetto. Mentre il codice è in esecuzione e il suo stato è in evoluzione, i ritardi multipli e possibilmente ridondanti vanno bene. Comunque per quanto riguarda questo schema, catturare un forte riferimento lì non fa nulla per il caso di auto-deallocazione prima dell'esecuzione del blocco, che può ancora accadere. Garantisce che self non venga deallocato durante l'esecuzione del blocco. Ciò importa se il blocco esegue operazioni asincrone dando una finestra affinché ciò avvenga.
Pierre Houston,

@smallduck, la tua spiegazione è fantastica. Ora lo capisco meglio. I libri non lo hanno trattato, grazie.
Huibin Zhang,

5
Questo non è un buon esempio di strongSelf, perché l'aggiunta esplicita di strongSelf è esattamente ciò che il runtime farebbe comunque: sulla linea doSomething, viene preso un riferimento forte per la durata della chiamata al metodo. Se weakSelf è già stato invalidato, il riferimento forte è zero e la chiamata del metodo è no-op. Dove strongSelf aiuta è se si dispone di una serie di operazioni o si accede a un campo membro ( ->), dove si desidera garantire di avere effettivamente un riferimento valido e mantenerlo continuamente nell'intero insieme di operazioni, ad esempioif ( strongSelf ) { /* several operations */ }
Ethan,

19

Come sottolinea Leo, il codice che hai aggiunto alla tua domanda non suggerirebbe un forte ciclo di riferimento (ovvero ciclo di mantenimento). Un problema relativo all'operazione che potrebbe causare un forte ciclo di riferimento sarebbe se l'operazione non viene rilasciata. Mentre lo snippet di codice suggerisce che non hai definito la tua operazione come concorrente, ma se lo hai, non verrebbe rilasciata se non hai mai pubblicato isFinished, o se avessi dipendenze circolari o qualcosa del genere. E se l'operazione non viene rilasciata, neanche il controller di visualizzazione verrà rilasciato. Suggerirei di aggiungere un punto di interruzione o NSLognel deallocmetodo dell'operazione e confermare che viene chiamato.

Tu hai detto:

Capisco il concetto di cicli di mantenimento, ma non sono del tutto sicuro di cosa accada in blocchi, quindi questo mi confonde un po '

I problemi del ciclo di conservazione (ciclo di riferimento forte) che si verificano con i blocchi sono proprio come i problemi del ciclo di conservazione con cui si ha familiarità. Un blocco manterrà riferimenti forti a tutti gli oggetti che compaiono all'interno del blocco e non rilascerà quei riferimenti forti fino a quando il blocco stesso non viene rilasciato. Pertanto, se i riferimenti di blocco self, o anche solo riferimenti a una variabile di istanza di self, manterranno un forte riferimento a sé, che non viene risolto fino al rilascio del blocco (o in questo caso, fino al NSOperationrilascio della sottoclasse.

Per ulteriori informazioni, consultare la sezione Evitare cicli di riferimento forti durante l'acquisizione di sé del documento Programmazione con Objective-C: Lavorare con i blocchi .

Se il tuo controller di visualizzazione non viene ancora rilasciato, devi semplicemente identificare dove risiede il riferimento sicuro non risolto (supponendo che tu abbia confermato che NSOperationsta per essere deallocato). Un esempio comune è l'uso di una ripetizione NSTimer. O qualche delegateoggetto personalizzato o altro che erroneamente mantiene un strongriferimento. Spesso puoi usare gli strumenti per rintracciare dove gli oggetti ottengono i loro riferimenti forti, ad esempio:

registra i conteggi dei riferimenti in Xcode 6

O in Xcode 5:

registrare i conteggi dei riferimenti in Xcode 5


1
Un altro esempio potrebbe essere se l'operazione viene mantenuta nel creatore del blocco e non rilasciata al termine. +1 sul bel scrivere!
Leo Natan,

@LeoNatan D'accordo, anche se lo snippet di codice lo rappresenta come una variabile locale che verrebbe rilasciata se utilizza ARC. Ma hai ragione!
Rob,

1
Sì, stavo solo facendo un esempio, come richiesto dall'OP nell'altra risposta.
Leo Natan,

A proposito, Xcode 8 ha "Debug Memory Graph", che è un modo ancora più semplice per trovare forti riferimenti a oggetti che non sono stati rilasciati. Vedi stackoverflow.com/questions/30992338/… .
Rob,

0

Alcune spiegazioni ignorano una condizione relativa al ciclo di mantenimento [Se un gruppo di oggetti è collegato da un cerchio di relazioni forti, si mantengono reciprocamente in vita anche se non ci sono riferimenti forti al di fuori del gruppo.] Per ulteriori informazioni, leggere il documento


-2

Ecco come puoi usare il sé all'interno del blocco:

// chiamata del blocco

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

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


};
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.