2012-06-12 2 views
6

Dalle Transitioning to ARC Release NotesPerché dobbiamo impostare __block variable su nil?

Qualificazioni consumo una tantum per evitare forti cicli di riferimento

È possibile utilizzare qualificazioni a vita per evitare forti cicli di riferimento. Per esempio , in genere se si dispone di un grafico di oggetti disposti in una gerarchia padre-figlio e i genitori devono rivolgersi ai propri figli e viceversa, quindi rendere la relazione genitore-figlio forte e il figlio-a -parent relationship debole. Altre situazioni potrebbero essere più sottili , in particolare quando coinvolgono oggetti di blocco.

Nella modalità di conteggio del riferimento manuale, __block id x; ha l'effetto di non trattenendo x. In modalità ARC, il valore predefinito di __block id x; è il mantenimento di x (solo come tutti gli altri valori). Per ottenere la modalità di conteggio del riferimento manuale sotto ARC, è possibile utilizzare __unsafe_unretained __block id x;. Come il nome __unsafe_unretained implica, tuttavia, avere una variabile non mantenuta è pericolosa (perché può penzolare) ed è quindi scoraggiata. Due opzioni migliori sono l'uso di __weak (se non è necessario supportare iOS 4 o OS X v10.6) o impostare il valore su nil per interrompere il ciclo di conservazione.

Ok, quindi cosa c'è di diverso nella variabile __block?

Perché impostare su nil qui? La variabile __block viene mantenuta due volte? Chi detiene tutto il riferimento? Il blocco? Il mucchio? Lo stack? Il filo? Il cosa?

Il seguente frammento di codice illustra questo problema utilizzando un motivo che viene talvolta utilizzato nel conteggio dei riferimenti manuale.

MyViewController *myController = [[MyViewController alloc] init…]; 

// ... 

myController.completionHandler = ^(NSInteger result) { 
    [myController dismissViewControllerAnimated:YES completion:nil]; 
}; 

[self presentViewController:myController animated:YES completion:^{ 
    [myController release]; 
}]; 

Come descritto, invece, è possibile utilizzare un qualificatore __block e impostare la variabile myController al nil nel gestore di completamento:

MyViewController * __block myController = [[MyViewController alloc] init…]; //Why use __block. my controller is not changed at all 

// ... 

myController.completionHandler = ^(NSInteger result) { 
    [myController dismissViewControllerAnimated:YES completion:nil]; 

    myController = nil; //Why set to nil here? Is __block variable retained twice? Who hold all the reference? The block? The heap? The stack? The thread? The what? 
}; 

Anche perché myController non è impostato su nil dal compilatore. Perché dobbiamo farlo? Sembra che il compilatore sappia quando myController non sarà più riutilizzato, ovvero quando il blocco scadrà.

risposta

14

Quando si dispone di codice di questo modulo:

object.block = ^{ 
    // reference object from inside the block 
    [object someMethodOrProperty]; 
}; 

object manterrà o copiare il blocco si dà ad esso. Ma il blocco stesso manterrà anche object perché è fortemente referenziato all'interno del blocco. Questo è un ciclo di conservazione. Anche dopo che il blocco ha terminato l'esecuzione, il ciclo di riferimento esiste ancora e né l'oggetto né il blocco possono essere deallocati. Ricorda che un blocco può essere chiamato più volte, quindi non può semplicemente dimenticare tutte le variabili a cui fa riferimento una volta terminato l'esecuzione.

Per interrompere questo ciclo, è possibile definire una variabile __block, che consente di modificarne il valore dall'interno del blocco, ad es.cambiandolo nil a rompere il ciclo:

__block id object = ...; 
object.block = ^{ 
    // reference object from inside the block 
    [object someMethodOrProperty]; 

    object = nil; 
    // At this point, the block no longer retains object, so the cycle is broken 
}; 

Quando si assegna object a nil alla fine del blocco, il blocco non sarà più mantenere object e il ciclo trattenere è rotto. Ciò consente ad entrambi gli oggetti di essere deallocati.

Un esempio concreto di questo è con la proprietà completionBlockNSOperation. Se si utilizza il completionBlock per accedere risultato di un'operazione, è necessario rompere il ciclo che viene creato conservano:

__block NSOperation *op = [self operationForProcessingSomeData]; 
op.completionBlock = ^{ 
    // since we strongly reference op here, a retain cycle is created 
    [self operationFinishedWithData:op.processedData]; 

    // break the retain cycle! 
    op = nil; 
} 

Come la documentazione descrive, ci sono una serie di altre tecniche è possibile utilizzare anche per rompere questi conservano cicli . Ad esempio, sarà necessario utilizzare una tecnica diversa in codice non ARC rispetto a quella del codice ARC.

+0

"Ma il blocco stesso manterrà anche l'oggetto perché è fortemente referenziato all'interno del blocco." Perché? Chiusura. –

+0

In che modo l'aggiunta di __block fa comunque la differenza? –

+0

Quando un blocco cattura un puntatore a un oggetto obiettivo-c, quell'oggetto verrà mantenuto a meno che non si usi '__weak' o' __unsafe_unretained' (o '__block' in un codice non ARC). –

0

preferisco questa soluzione

typeof(self) __weak weakSelf = self; 
self.rotationBlock = ^{ 
    typeof (weakSelf) __strong self = weakSelf; 

    [self yourCodeThatReferenceSelf]; 
}; 

ciò che accade è che il blocco catturerà sé come un debole riferimento e non ci sarà alcun essere trattenere ciclo. self all'interno del blocco viene quindi ridefinito come __strong self = weakSelf prima dell'esecuzione del codice. Ciò impedisce a se stesso di essere rilasciato mentre il blocco viene eseguito.