Mantieni il ciclo su `self` con blocchi


167

Temo che questa domanda sia piuttosto semplice, ma penso che sia pertinente per molti programmatori di Objective-C che stanno entrando in blocchi.

Quello che ho sentito è che, poiché i blocchi acquisiscono variabili locali a cui fanno riferimento al loro interno come constcopie, l'utilizzo selfall'interno di un blocco può comportare un ciclo di mantenimento, nel caso in cui quel blocco venga copiato. Quindi, dovremmo usare __blockper forzare il blocco a gestire direttamente selfinvece di averlo copiato.

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

invece di solo

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

Quello che mi piacerebbe sapere è il seguente: se questo è vero, c'è un modo per evitare la bruttezza (oltre all'utilizzo di GC)?


3
Mi piace chiamare i miei selfdelegati thissolo per capovolgere le cose. In JavaScript chiamo le mie thischiusure self, quindi è bello ed equilibrato. :)
devios1

Mi chiedo se c'è qualche azione equivalente da fare se sto usando i blocchi Swift
Ben Lu

@BenLu assolutamente! nelle chiusure Swift (e le funzioni che passano intorno a quella menzione di sé implicitamente o esplicitamente) manterranno il sé. A volte questo è desiderato, e altre volte crea un ciclo (perché la chiusura stessa viene posseduta dal sé (o posseduta da qualcosa di sé). La ragione principale per cui ciò accade è a causa di ARC.
Ethan,

1
Per evitare problemi, il modo appropriato di definire 'self' da usare in un blocco è '__typeof (self) __weak weakSelf = self;' per avere un riferimento debole.
XLE_22

Risposte:


169

A rigor di termini, il fatto che si tratti di una copia const non ha nulla a che fare con questo problema. I blocchi manterranno tutti i valori obj-c che vengono acquisiti quando vengono creati. Accade solo che la soluzione alternativa per il problema const-copy sia identica alla soluzione alternativa per il problema di conservazione; vale a dire, usando il__block classe di archiviazione per la variabile.

In ogni caso, per rispondere alla tua domanda, qui non c'è vera alternativa. Se stai progettando la tua API basata su blocco e ha senso farlo, potresti far passare il valore al bloccoself in come argomento. Sfortunatamente, questo non ha senso per la maggior parte delle API.

Nota che fare riferimento a un ivar ha lo stesso identico problema. Se devi fare riferimento a un ivar nel tuo blocco, usa invece una proprietà o usa bself->ivar.


Addendum: durante la compilazione come ARC, __blocknon interrompe più i cicli di conservazione. Se stai compilando per ARC, devi usare__weak o __unsafe_unretainedinvece.


Nessun problema! Se questo rispondesse alla tua domanda con soddisfazione, ti sarei grato se tu potessi scegliere questa come la risposta corretta alla tua domanda. Altrimenti, per favore fatemi sapere come posso rispondere meglio alla vostra domanda.
Lily Ballard,

4
Nessun problema, Kevin. SO ti ritarda a scegliere immediatamente una risposta a una domanda, quindi sono dovuto tornare un po 'più tardi. Saluti.
Jonathan Sterling,

__unsafe_unretained id bself = sé;
caleb,

4
@JKLaiho: Certo, __weakva bene lo stesso. Se sai per certo che l'oggetto non può essere fuori dall'ambito quando viene invocato il blocco, allora __unsafe_unretainedè sempre leggermente più veloce, ma in generale ciò non farà differenza. Se lo usi __weak, assicurati di lanciarlo in una __strongvariabile locale e testalo per non nilprima di fare qualcosa con esso.
Lily Ballard,

2
@Rpranata: Sì. __blockL 'effetto collaterale di non conservare e rilasciare era puramente dovuto all'incapacità di ragionare adeguatamente su questo. Con ARC, il compilatore ha acquisito tale capacità e quindi __blockmantiene e rilascia. Se è necessario evitarlo, è necessario utilizzare __unsafe_unretained, che indica al compilatore di non eseguire ritardi o rilasci sul valore nella variabile.
Lily Ballard,

66

Usa solo:

__weak id weakSelf = self;

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

Per ulteriori informazioni: WWDC 2011 - Blocks and Grand Central Dispatch in Practice .

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

Nota: se non funziona puoi provare

__weak typeof(self)weakSelf = self;

2
E l'hai trovato per caso :)?
Tieme,

2
Puoi controllare il video qui - developer.apple.com/videos/wwdc/2011/…
nanjunda,

Sei in grado di fare riferimento a te stesso all'interno di "someOtherMethod"? Si riferirebbe a se stesso a quel punto o creerebbe un ciclo di mantenimento?
Oren,

Ciao @Oren, se provi a fare riferimento a te stesso all'interno di "someOtherMethod" riceverai un avviso Xcode. Il mio approccio fa solo un debole riferimento a se stesso.
3lvis

1
Ho ricevuto un avviso solo quando ho fatto riferimento a me stesso direttamente nel blocco. Inserire se stessi all'interno di SomeAltroMethod non ha causato alcun avvertimento. È perché xcode non è abbastanza intelligente o non è un problema? Fare riferimento a se stessi all'interno di SomeAltroMetodo si riferirebbe già a debole Sé poiché è su questo che stai chiamando il metodo?
Oren,

22

Questo potrebbe essere ovvio, ma devi solo fare il brutto self alias quando sai che otterrai un ciclo di mantenimento. Se il blocco è solo una cosa a colpo singolo, penso che puoi tranquillamente ignorare il mantenimento self. Il brutto caso è quando hai il blocco come interfaccia di callback, per esempio. Come qui:

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

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

@implementation AudioProcessor

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

Qui l'API non ha molto senso, ma avrebbe senso quando si comunica con una superclasse, ad esempio. Conserviamo il gestore buffer, il gestore buffer ci mantiene. Confronta con qualcosa del genere:

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

In queste situazioni non faccio l' selfaliasing. Si ottiene un ciclo di mantenimento, ma l'operazione è di breve durata e alla fine il blocco uscirà dalla memoria, interrompendo il ciclo. Ma la mia esperienza con i blocchi è molto piccola e potrebbe essere che l' selfaliasing sia una buona pratica a lungo termine.


6
Buon punto. È solo un ciclo di mantenimento se il sé mantiene vivo il blocco. Nel caso di blocchi che non vengono mai copiati o blocchi con una durata limitata garantita (ad es. Il blocco di completamento per un'animazione UIView), non devi preoccuparti.
Lily Ballard il

6
In linea di principio, hai ragione. Tuttavia, se dovessi eseguire il codice nell'esempio, andresti in crash. Le proprietà del blocco devono sempre essere dichiarate come copy, non retain. Se lo sono retain, non c'è alcuna garanzia che verranno spostati dallo stack, il che significa che quando lo eseguirai, non sarà più lì. (e la copia e il blocco già copiato sono ottimizzati per essere conservati)
Dave DeLong

Ah, certo, un errore di battitura. Ho attraversato la retainfase qualche tempo fa e ho realizzato rapidamente la cosa che stai dicendo :) Grazie!
zoul

Sono abbastanza sicuro che retainsia completamente ignorato per i blocchi (a meno che non si siano già spostati dallo stack con copy).
Steven Fisher

@Dave DeLong, No, non si arresterebbe in modo anomalo poiché @property (keep) viene utilizzato solo come riferimento a un oggetto, non per il blocco .. Non è necessario utilizzare una copia qui ...
DeniziOS

20

Pubblicare un'altra risposta perché questo è stato un problema anche per me. Inizialmente pensavo di dover usare blockSelf ovunque ci fosse un riferimento personale all'interno di un blocco. Questo non è il caso, è solo quando l'oggetto stesso ha un blocco al suo interno. E infatti, se usi blockSelf in questi casi, l'oggetto può essere deallocato prima di ottenere il risultato dal blocco e poi si arresterà in modo anomalo quando proverà a chiamarlo, quindi chiaramente vuoi che il sé venga mantenuto fino alla risposta ritorna.

Il primo caso dimostra quando si verificherà un ciclo di mantenimento perché contiene un blocco a cui fa riferimento il blocco:

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

Non hai bisogno di blockSelf nel secondo caso perché l'oggetto chiamante non ha un blocco che causerà un ciclo di mantenimento quando fai riferimento a self:

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

Questo è un malinteso comune e può essere pericoloso, perché i blocchi che devono essere conservati selfpotrebbero non essere dovuti a un'applicazione eccessiva di questa correzione. Questo è un buon esempio di come evitare cicli di conservazione nel codice non ARC, grazie per la pubblicazione.
Carl Veazey,

9

Ricorda anche che possono verificarsi cicli di mantenimento se il tuo blocco fa riferimento a un altro oggetto che quindi mantieneself .

Non sono sicuro che Garbage Collection possa aiutare in questi cicli di conservazione. Se l'oggetto che conserva il blocco (che chiamerò l'oggetto server) sopravvive self(l'oggetto client), il riferimento aself all'interno del blocco non sarà considerato ciclico fino al rilascio dell'oggetto di conservazione stesso. Se l'oggetto server sopravvive di gran lunga ai suoi client, potresti avere una significativa perdita di memoria.

Dal momento che non esistono soluzioni pulite, consiglierei le seguenti soluzioni alternative. Sentiti libero di sceglierne uno o più per risolvere il problema.

  • Utilizzare i blocchi solo per il completamento e non per eventi a tempo indeterminato. Ad esempio, utilizzare i blocchi per metodi simili doSomethingAndWhenDoneExecuteThisBlock:e non per metodi similisetNotificationHandlerBlock: . I blocchi utilizzati per il completamento hanno una durata definita e devono essere rilasciati dagli oggetti server dopo essere stati valutati. Questo impedisce al ciclo di mantenimento di sopravvivere troppo a lungo anche se si verifica.
  • Fai quella danza di riferimento debole che hai descritto.
  • Fornire un metodo per ripulire l'oggetto prima che venga rilasciato, che "disconnetta" l'oggetto dagli oggetti server che potrebbero contenere riferimenti ad esso; e chiama questo metodo prima di chiamare il rilascio sull'oggetto. Mentre questo metodo è perfetto se il tuo oggetto ha un solo client (o è un singleton in un certo contesto), ma si romperà se ha più client. Fondamentalmente stai sconfiggendo il meccanismo di conteggio del mantenimento qui; questo è simile a chiamare deallocinvece di release.

Se stai scrivendo un oggetto server, prendi gli argomenti a blocchi solo per il completamento. Non accettare argomenti a blocchi per callback, come setEventHandlerBlock:. Invece, ripiega sul modello delegato classico: crea un protocollo formale e pubblicizza un setEventDelegate:metodo. Non conservare il delegato. Se non si desidera nemmeno creare un protocollo formale, accettare un selettore come callback delegato.

E, infine, questo schema dovrebbe suonare allarmi:

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

Se stai provando a sganciare blocchi che potrebbero riferirsi selfdall'interno dealloc, sei già nei guai. deallocpotrebbe non essere mai chiamato a causa del ciclo di conservazione causato dai riferimenti nel blocco, il che significa che il tuo oggetto semplicemente perderà fino a quando l'oggetto server non viene deallocato.


GC aiuta se lo usi in modo __weakappropriato.
tc.

La tracciabilità della spazzatura può ovviamente gestire i cicli di conservazione. La conservazione dei cicli è solo un problema per gli ambienti di conteggio dei riferimenti
accesso

Proprio per questo lo sanno tutti, la garbage collection è stata deprecata in OS X v10.8 a favore di Automatic Reference Counting (ARC) e dovrebbe essere rimossa in una versione futura di OS X ( developer.apple.com/library/mac/#releasenotes / Obiettivo C / ... ).
Ricardo Sanchez-Saez,

1

__block __unsafe_unretainedi modificatori suggeriti nel post di Kevin possono causare un'eccezione di accesso errato in caso di blocco eseguito in un thread diverso. È meglio usare solo il modificatore __block per la variabile temp e renderlo nullo dopo l'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;
}];

Non sarebbe davvero più sicuro usare solo __weak anziché __block per evitare la necessità di nilling della variabile dopo averla usata? Voglio dire, questa soluzione è ottima se si desidera interrompere altri tipi di cicli, ma certamente non vedo alcun vantaggio particolare per i cicli di "auto" mantenimento su di essa.
ale0xB,

Non puoi utilizzare __weak se il target della tua piattaforma è iOS 4.x. Inoltre a volte è necessario che il codice nel blocco sia stato eseguito per l'oggetto valido, non per zero.
b1gbr0,

1

Puoi usare la libreria libextobjc. È abbastanza popolare, ad esempio viene utilizzato in ReactiveCocoa. https://github.com/jspahrsummers/libextobjc

Fornisce 2 macro @weakify e @strongify, quindi puoi avere:

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

Questo impedisce un riferimento forte diretto, quindi non entriamo in un ciclo di mantenimento verso se stessi. Inoltre, impedisce a se stesso di azzerarsi a metà, ma riduce ancora correttamente il conteggio dei ritardi. Altro in questo link: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
Prima di mostrare il codice semplificato sarebbe meglio sapere cosa c'è dietro, tutti dovrebbero conoscere le due vere righe di codice.
Alex Cio,

0

Cosa ne pensi di questo?

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

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

Non ricevo più l'avviso del compilatore.


-1

Blocco: si verificherà un ciclo di mantenimento perché contiene un blocco a cui fa riferimento il blocco; Se si effettua la copia di blocco e si utilizza una variabile membro, self manterrà.

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.