Mantieni il ciclo su `self` con blocchi

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

Quello che ho sentito è che, poiché i blocchi acquisiscono variabili locali a loro riferimento come copie const , l’uso di self all’interno di un blocco può comportare un ciclo di conservazione, nel caso in cui il blocco venga copiato. Quindi, dovremmo usare __block per forzare il blocco a trattare direttamente con self invece 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 (a parte l’uso di GC)?

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 catturati quando vengono creati. Accade semplicemente che la soluzione alternativa per il problema della copia const sia identica alla soluzione alternativa per il problema di conservazione; vale a dire, utilizzando la class di archiviazione __block per la variabile.

In ogni caso, per rispondere alla tua domanda, non c’è una vera alternativa qui. Se stai progettando la tua API basata su blocchi, e ha senso farlo, potresti far sì che il blocco superi il valore di self come argomento. Sfortunatamente, questo non ha senso per la maggior parte delle API.

Si prega di notare che fare riferimento a un ivar ha lo stesso identico problema. Se hai bisogno di fare riferimento a un ivar nel tuo blocco, usa invece una proprietà o usa bself->ivar .


Addendum: durante la compilazione come ARC, __block non interrompe più i cicli di conservazione. Se stai compilando per ARC, devi usare __weak o __unsafe_unretained .

Basta usare:

 __weak id weakSelf = self; [someObject someMethodWithBlock:^{ [weakSelf someOtherMethod]; }]; 

Per maggiori informazioni: WWDC 2011 – Blocchi e Grand Central Dispatch in pratica .

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

Nota: se non funziona, puoi provare

 __weak typeof(self)weakSelf = self; 

Questo potrebbe essere ovvio, ma devi solo fare il brutto self alias quando sai che otterrai un ciclo di conservazione. Se il blocco è solo una cosa a un solo colpo, allora penso che tu possa tranquillamente ignorare il mantenimento su self . Il caso negativo è quando si ha 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 superclass, per esempio. Manteniamo il gestore di buffer, il gestore di buffer ci conserva. 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’ self aliasing. Si ottiene un ciclo di conservazione, ma l’operazione è di breve durata e il blocco uscirà dalla memoria, interrompendo il ciclo. Ma la mia esperienza con i blocchi è molto piccola e potrebbe essere che l’ self aliasing sia una buona pratica a lungo termine.

Pubblicare un’altra risposta perché questo era 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’object stesso ha un blocco in esso. In effetti, se si usa blockSelf in questi casi l’object può essere dealloc’d prima di ottenere il risultato dal blocco e poi si blocca quando tenta di chiamarlo, quindi chiaramente si vuole mantenere il self fino alla risposta ritorna.

Il primo caso dimostra quando si verificherà un ciclo di conservazione poiché contiene un blocco a cui si fa riferimento nel blocco:

 #import  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 è necessario blockSelf nel secondo caso perché l’object chiamante non ha un blocco in esso che causerà un ciclo di conservazione quando si fa riferimento a se stessi:

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

Ricorda anche che i cicli di conservazione possono verificarsi se il tuo blocco si riferisce a un altro object che poi mantiene self .

Non sono sicuro che Garbage Collection possa aiutare in questi cicli di conservazione. Se l’object che conserva il blocco (che chiamerò l’object server) sopravvive self (l’object client), il riferimento a self all’interno del blocco non sarà considerato ciclico finché l’object di conservazione stesso non viene rilasciato. Se l’object server supera di gran lunga i propri client, è ansible che si verifichi una perdita di memoria significativa.

Poiché non ci sono soluzioni pulite, suggerirei i seguenti metodi alternativi. Sentiti libero di sceglierne uno o più per risolvere il problema.

  • Usa i blocchi solo per il completamento e non per gli eventi aperti. Ad esempio, usa i blocchi per metodi come doSomethingAndWhenDoneExecuteThisBlock: setNotificationHandlerBlock: e non metodi come setNotificationHandlerBlock: I blocchi utilizzati per il completamento hanno una durata definita della vita e devono essere rilasciati dagli oggetti server dopo che sono stati valutati. Ciò impedisce al ciclo di conservazione di vivere troppo a lungo anche se si verifica.
  • Fai quella danza di riferimento debole che hai descritto.
  • Fornire un metodo per ripulire l’object prima che venga rilasciato, che “disconnette” l’object dagli oggetti server che potrebbero contenere riferimenti ad esso; e chiama questo metodo prima di chiamare il rilascio sull’object. Mentre questo metodo è perfettamente adatto se il tuo object ha un solo client (o è un singleton in un contesto), ma si interromperà se ha più client. Stai praticamente sconfiggendo il meccanismo di conteggio dei ritardi qui; questo è come chiamare dealloc invece di release .

Se si sta scrivendo un object server, accettare gli argomenti del blocco solo per il completamento. Non accettare argomenti di blocco per i callback, come setEventHandlerBlock: Invece, setEventDelegate: al classico modello delegato: creare un protocollo formale e pubblicizzare un metodo setEventDelegate: . 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 gli allarmi:

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

Se stai cercando di sganciare blocchi che potrebbero riferirsi a te da dealloc , sei già nei guai. dealloc non può mai essere chiamato a causa del ciclo di conservazione causato dai riferimenti nel blocco, il che significa che il proprio object andrà in perdita fino a quando l’object server non viene deallocato.

__block __unsafe_unretained modificatori __block __unsafe_unretained suggeriti nel post di Kevin possono causare l’eccezione di accesso __block __unsafe_unretained 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; }]; 

È ansible utilizzare la libreria libextobjc. È abbastanza popolare, è usato in ReactiveCocoa per esempio. https://github.com/jspahrsummers/libextobjc

Fornisce 2 macro @weakify e @strongify, così puoi avere:

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

Ciò impedisce un riferimento diretto forte, quindi non entriamo in un ciclo di conservazione per sé. Inoltre, impedisce a se stesso di diventare a metà strada, ma diminuisce ancora correttamente il conteggio dei ritiri. Maggiori informazioni in questo link: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

Cosa ne pensi di questo?

 - (void) foo { __weak __block me = self; myBlock = ^ { [[me someProp] someMessage]; } ... } 

Non ricevo più l’avviso del compilatore.

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