2012-06-12 6 views
6

De los Transitioning to ARC Release Notes¿Por qué tenemos que establecer __block variable en nil?

Calificadores uso en la vida para evitar los ciclos de referencia fuertes

Puede utilizar calificadores de por vida para evitar ciclos de referencia fuertes. Para el ejemplo , normalmente si tiene un gráfico de objetos organizados en una jerarquía de elementos primarios y secundarios y los padres necesitan referirse a sus hijos y viceversa, entonces la relación padre-hijo es fuerte y hijo-a -relacion parental débil Otras situaciones pueden ser más sutiles, particularmente cuando involucran objetos de bloque.

En el modo de recuento de referencia manual, __block id x; tiene el efecto de no que retiene x. En el modo ARC, __block id x; se predetermina a retener x (solo como todos los demás valores). Para obtener el comportamiento del modo de recuento de referencia manual bajo ARC, puede usar __unsafe_unretained __block id x;. Como el nombre __unsafe_unretained implica, sin embargo, tener una variable no retenida es peligroso (porque puede colgar) y es por lo tanto desaconsejado. Dos mejores opciones son usar __weak (si es no necesita admitir iOS 4 u OS X v10.6), o establecer el valor __block en nil para interrumpir el ciclo de retención.

Bien, entonces ¿qué hay de diferente en __block variable?

¿Por qué configurar nil aquí? ¿La variable __block se retiene dos veces? ¿Quién tiene toda la referencia? ¿El bloque? El montón? ¿La pila? ¿La amenaza? ¿El qué?

El siguiente fragmento de código ilustra este problema utilizando un patrón que a veces se utiliza en el recuento manual de referencias.

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

// ... 

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

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

Como se ha descrito, en cambio, se puede utilizar un calificador __block y establecer la variable myController a nil en el controlador de ejecución:

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

También por qué myController no se ajusta a nil por el compilador. ¿Por qué tenemos que hacer eso? Parece que el compilador sabe cuándo myController ya no se volverá a utilizar, es decir, cuando caduque el bloque.

Respuesta

14

Cuando se tiene código de este formulario:

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

object retendrá o copiar el bloque que da a la misma. Pero el bloque también retendrá object porque está fuertemente referenciado dentro del bloque. Este es un ciclo de retención. Incluso después de que el bloque haya terminado de ejecutarse, el ciclo de referencia aún existe y ni el objeto ni el bloque pueden desasignarse. Recuerde que un bloque se puede llamar varias veces, por lo que no puede simplemente olvidar todas las variables a las que hace referencia una vez que ha finalizado la ejecución.

Para romper este ciclo, puede definir object como una variable __block, que le permite cambiar su valor desde el interior del bloque, p.cambiándolo a nil para romper el 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 
}; 

Cuando asignamos a objectnil al final del bloque, el bloque ya no retener object y el ciclo se rompe retener. Esto permite que ambos objetos sean desasignados.

Un ejemplo concreto de esto es con NSOperation 's completionBlock propiedad. Si se utiliza el completionBlock acceder a consecuencia de una operación, es necesario romper el ciclo que se crearon conservan:

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

A medida que la documentación se describen, hay una serie de otras técnicas también se puede utilizar para romper éstos conservan ciclos . Por ejemplo, necesitará usar una técnica diferente en código que no sea ARC de la que usaría en el código ARC.

+0

"Pero el bloque en sí también retendrá el objeto porque está fuertemente referenciado dentro del bloque". ¿Por qué? Cierre. –

+0

¿De qué manera la adición de __block hace alguna diferencia de todos modos? –

+0

Cuando un bloque captura un puntero a un objeto objetivo-c, ese objeto se mantendrá a menos que use '__weak' o' __unsafe_unretained' (o '__block' en código que no sea ARC). –

0

prefiero esta solución

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

    [self yourCodeThatReferenceSelf]; 
}; 

Lo que pasa es que el bloque capturará uno mismo como un débil referencia y no habrá ningún ser retener ciclo. El self dentro del bloque se redefine como __strong self = weakSelf antes de que se ejecute tu código. Esto evita que el self se libere mientras tu bloque se ejecuta.

Cuestiones relacionadas