objective c - ¿Debo seguir copiando/Block_copy los bloques bajo ARC?
objective-c automatic-ref-counting (3)
ARC copiará el bloque automáticamente. De la documentación de Conteo Automático de Referencia de Objective-C de clang:
Con la excepción de
__strong
realizadas como parte de la inicialización de una variable de parámetro__strong
o la lectura de una variable__weak
, siempre que estas semánticas__weak
retención de un valor de tipo de puntero de bloque, tiene el efecto de unaBlock_copy
deBlock_copy
. El optimizador puede eliminar dichas copias cuando vea que el resultado se usa solo como un argumento para una llamada.
Por lo tanto, los bloques utilizados solo como argumentos para la función o las llamadas a métodos pueden permanecer como bloques apilados, pero en cualquier otro lugar que ARC retenga el bloque, copiará el bloque. Esto es implementado por el compilador que emite una llamada a objc_retainBlock()
, cuya implementation es:
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
Todavía es una buena idea declarar que las propiedades de bloque tienen una semántica de copia, ya que, de hecho, se copiará un bloque asignado a una propiedad segura. Apple recomienda esto también:
Debe especificar la copia como el atributo de propiedad, ya que un bloque debe copiarse para realizar un seguimiento de su estado capturado fuera del alcance original. Esto no es algo de lo que deba preocuparse al usar el conteo automático de referencias, ya que ocurrirá automáticamente, pero es una buena práctica que el atributo de propiedad muestre el comportamiento resultante.
Tenga en cuenta que dado que ARC proporciona esta función de copia en retención, depende solo de la disponibilidad de ARC o ARCLite y, por lo demás, no requiere una versión particular del sistema operativo o OS_OBJECT_USE_OBJC
.
Acabo de encontrarme con el siguiente tema de SO: ¿Por qué deberíamos copiar los bloques en lugar de retenerlos? que tiene la siguiente frase:
Sin embargo, a partir de iOS 6 se tratan como objetos normales, por lo que no debe preocuparse.
Estaba realmente confundido por esta afirmación, por eso estoy preguntando: ¿esta afirmación realmente implica que los desarrolladores de Objective-C no necesitan hacerlo?
@property (copy) blockProperties
o
[^(...){...) {} copy]
¿Para copiar bloques y sus contenidos de pila a montón más?
Espero que la descripción que he hecho sea clara.
Por favor, sea detallado.
Preguntas similares
Bajo ARC, ¿los bloques se copian automáticamente cuando se asignan a un ivar directamente? .
Editar:
Resultó que el examen de las direcciones de las variables "capturadas" es difícil de interpretar, y no siempre es apropiado averiguar si un Bloque se ha copiado al montón o aún reside en el montón. Aunque la Especificación de bloques que se proporciona aquí BLOQUEAR LA ESPECIFICACIÓN DE LA IMPLEMENTACIÓN , describirá suficientemente los hechos, intento un enfoque completamente diferente:
¿Qué es un bloque de todos modos?
Este es un resumen de la especificación oficial ESPECIFICACIÓN DE IMPLEMENTACIÓN DE BLOQUE :
Existe un bloque de código (como una función) y una estructura que contiene varios grupos de datos, indicadores y punteros de función Y una sección de longitud variable para "variables capturadas".
Tenga en cuenta que esta estructura es privada y su implementación está definida.
Un bloque se puede definir en el ámbito de la función, donde esta estructura se crea en la memoria local de la pila, o se puede definir en el ámbito global o estático, donde la estructura se crea en el almacenamiento estático.
Un bloque puede "importar" otras referencias de bloque, otras variables y __block
variables modificadas.
Cuando un bloque hace referencia a otras variables, se importarán:
Una variable local (automática) de la pila, se "importará" por medio de hacer una "copia constante".
Una variable modificada
__block
se importará mediante la asignación de un puntero a la dirección de esa variable incluida en otra estructura.Las variables globales serán referenciadas simplemente (no "importadas").
Si se importará una variable, la "variable capturada" vive en la estructura mencionada en la sección de longitud variable. Es decir, la "contraparte" de la variable automática (que vive fuera del bloque) tiene un almacenamiento dentro de la estructura del bloque.
Dado que esta "variable capturada" es de solo lectura, el compilador puede aplicar algunas optimizaciones: por ejemplo, solo necesitamos una instancia de la variable capturada en el montón, si alguna vez necesitamos una copia del bloque.
El valor de las variables capturadas se establecerá cuando se evalúe la expresión literal del bloque. Esto también implica que el almacenamiento de las variables capturadas debe poder escribirse (es decir, no hay una sección de código).
La vida útil de las variables capturadas es la de una función: cada invocación de ese bloque requerirá una nueva copia de estas variables.
Las variables capturadas en bloques que viven en la pila se destruyen cuando el programa abandona la instrucción compuesta del bloque.
Las variables capturadas en bloques que viven en el montón se destruyen cuando se destruye el bloque.
Según el Block API, versiones 3.5:
Inicialmente, cuando se crea un bloque literal, esta estructura existirá en la pila:
Cuando se evalúa una expresión literal de bloque, la estructura basada en la pila se inicializa de la siguiente manera:
Una estructura de descriptor estático se declara y se inicializa de la siguiente manera:
a. El puntero a la función invocar se establece en una función que toma la estructura del Bloque como su primer argumento y el resto de los argumentos (si los hay) al Bloque y ejecuta la instrucción compuesta del Bloque.
segundo. El campo de tamaño se establece en el tamaño de la siguiente estructura literal de Bloque.
do. Los punteros de las funciones copy_helper y dispose_helper se configuran en funciones auxiliares respectivas si son requeridas por el literal de Bloque.
Se crea una estructura de datos literal de bloque de pila (o global) y se inicializa de la siguiente manera:
a. El campo isa se establece en la dirección del _NSConcreteStackBlock externo, que es un bloque de memoria no inicializada suministrada en libSystem, o _NSConcreteGlobalBlock si este es un literal de bloque de nivel de archivo o estático.
segundo. El campo de indicadores se establece en cero a menos que haya variables importadas en el Bloque que necesiten funciones de ayuda para las operaciones Block_copy () y Block_release () a nivel de programa, en cuyo caso se establece el bit de indicadores (1 << 25).
Tenga en cuenta que esto es para los literales de bloque .
De acuerdo con las extensiones de Objective-C a los bloques , el compilador tratará los bloques como objetos .
Observaciones
Ahora, es difícil elaborar código de prueba que demuestre estas afirmaciones. Por lo tanto, parece mejor usar el depurador y establecer puntos de interrupción simbólicos en funciones relevantes
_Block_copy_internal
ymalloc
(que debe habilitarse solo después de que se haya alcanzado el primer punto de interrupción)
y luego ejecute el código de prueba adecuado (como los fragmentos de código a continuación) y examine lo que sucede:
En el siguiente fragmento de código, creamos un literal de bloque y lo pasamos como parámetro a una función que lo llama:
typedef void (^block_t)(void);
void func(block_t block) {
if (block) {
block();
}
}
void foo(int param)
{
int x0 = param;
func(^{
int y0 = x0;
printf("Hello block 1/n");
printf("Address of auto y0: %p/n", &y0);
printf("Address of captured x0: %p/n", &x0);
});
}
La salida es la siguiente:
Hello block 1
Address of auto y0: 0x7fff5fbff8dc
Address of captured x0: 0x7fff5fbff940
La dirección de la variable "capturada" x0
indica claramente que vive en la pila.
También hemos establecido un punto de interrupción en _Block_copy_internal
- sin embargo, no será alcanzado. Esto indica que el Bloque no se ha copiado en el montón. Se puede hacer otra prueba con los instrumentos, que no muestran las asignaciones en la función foo
.
Ahora, si creamos e inicializamos una variable de bloque, parece que la estructura de datos de Bloque del literal de Bloque original, que se creó inicialmente en la pila, se copiará en el montón:
int capture_me = 1;
dispatch_block_t block = ^{ int y = capture_me; };
Este ejemplo copia el bloque que se creó inicialmente en la pila. Esto puede suceder simplemente debido a ARC y al hecho de que tenemos un literal de bloque en la mano derecha, y al bloque de variable de block
en la mano izquierda se le asignará el literal de bloque, lo que resulta en una operación Block_copy
. Esto hace que los bloques se parezcan a los objetos normales de Objective-C.
Las Asignaciones trazadas con Instrumentos en este código similar a continuación
void foo(int param)
{
dispatch_queue_t queue = dispatch_queue_create("queue", 0);
int x0 = param;
dispatch_block_t block = ^{
int y0 = x0;
printf("Hello block 1/n");
printf("Address of auto y0: %p/n", &y0);
printf("Address of captured x0: %p/n", &x0);
};
block();
}
Mostrar, que el Bloque será efectivamente copiado:
Notable
Cuando un bloque no captura ninguna variable, es como una función normal. Entonces tiene sentido aplicar una optimización, donde una operación
Block_copy
no hace nada, ya que no hay nada que copiar. clang implementa esto haciendo de tales bloques un "bloque global".Al enviar una
copy
a una variable de bloque y asignar el resultado a otra variable de bloque, por ejemplo:dispatch_block_t block = ^{ int y0 = capture_me; }; dispatch_block_t otherBlock = [block copy];
copy
será una operación bastante barata , ya que el bloque deblock
ya ha asignado almacenamiento para la estructura del bloque que se puede compartir. Por lo tanto, lacopy
no necesita asignar almacenamiento de nuevo.
Volver a la pregunta
Para responder la pregunta si necesitamos copiar explícitamente un bloque en ciertas circunstancias, por ejemplo:
@property (copy) block_t completion
[^{...} copy]
Bueno, una vez que se haya copiado el bloque, sin importar cuándo, y luego se le asignará la variable objetivo (una variable de Bloque), deberíamos estar siempre seguros sin copiar explícitamente el bloque, ya que ya está en el montón.
En el caso de la propiedad de bloque, debería ser seguro si simplemente quisiéramos:
@property dispatch_block_t completion;
y entonces:
foo.completion = ^{ x = capture_me; ... };
Esto debería ser seguro ya que al asignar un literal de bloque (que vive en la pila) la variable de bloque subyacente _completion
, copiará el bloque en el montón.
No obstante, aún recomendaría usar la copy
atributos, ya que la documentación oficial lo sigue recomendando como una práctica recomendada, y también es compatible con las API más antiguas donde los Bloques no se comportan como los objetos normales de Objective-C y donde no hay ARC.
Las API de los sistemas existentes también se ocuparán de copiar un Bloque si es necesario:
dispatch_async(queue, ^{int x = capture_me;});
dispatch_async () hará la copia por nosotros. Entonces, no tenemos que preocuparnos.
Otros escenarios son más sutiles:
dispatch_block_t block;
if (condition) {
block = ^{ ... };
}
else {
block = ^{ ... };
}
dispatch_sync(queue, block);
Pero en realidad, esto es seguro: el literal de bloque se copiará y se le asignará el bloque de variable de block
.
Este ejemplo puede parecer incluso aterrador:
int x0 = param;
NSArray* array = [NSArray arrayWithObject:^{
int y0 = x0;
printf("Hello block 1/n");
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
block_t block = array[0];
block();
});
Pero parece que el literal del bloque se copiará correctamente como un efecto de asignar el argumento (el bloque) al parámetro (un id
) en el método arrayWithObject:
que copia el literal del bloque en el montón:
Además, NSArray
conservará el objeto pasado como parámetro en el método arrayWithObject:
Esto provoca otra llamada a Block_copy()
, sin embargo, dado que el Bloque ya está en el montón, esta llamada no asigna el almacenamiento.
Esto indica que un mensaje de "retención" enviado a un literal de Bloque en algún lugar de la implementación de arrayWithObject:
también copiaría el Bloque.
Entonces, ¿cuándo necesitamos copiar explícitamente un Bloque?
Un literal de bloque , por ejemplo, la expresión:
^{...}
Creará la estructura de bloques en la pila.
Entonces, realmente necesitamos hacer una copia solo en los casos en que necesitamos el Bloque en el montón y cuando esto no sucede "automáticamente". Sin embargo, prácticamente no hay escenarios donde este es el caso. Incluso en los casos en los que pasamos un Bloque como parámetro a un método que no tiene idea de que es un Bloque literal y requiere una copy
primero (por ejemplo: arrayWithObject:
, y posiblemente el receptor envíe solo un mensaje de retención al objeto dado en El parámetro, el Bloque se copiará en el montón primero.
Los ejemplos en los que podríamos usar explícitamente copy
, es donde no asignamos un literal de bloque a una variable de bloque o una id
, y por lo tanto ARC no puede darse cuenta de que tiene que hacer una copia del objeto de bloque o tiene que enviar "retener".
En este caso, la expresión.
[^{...} copy];
producirá un Bloque lanzado automáticamente cuya estructura reside en el montón.
Mi respuesta original fue incorrecta. La respuesta editada no es una respuesta, sino más bien una cuestión de "es realmente una buena pregunta".
Por favor, vea la respuesta de Matt para obtener referencias reales de por qué las cosas son como son.
Después de probar el siguiente código en iOS7:
int stackVar;
void (^someBlock)() = ^(){};
NSLog(@"int: %x/nblock: %x,/ncopied: %x", (unsigned int)&stackVar, (unsigned int)someBlock, (unsigned int)[someBlock copy]);
Tengo esto:
int: bfffda70
block: 9d9948
copied: 9d9948
Lo que en gran medida significa que los bloques creados y asignados a las variables de pila en realidad ya están en un montón y copiarlos no afecta a nada.
Esto, sin embargo, no está respaldado por ninguna fuente oficial, ya que todavía afirman que los bloques que se crean en la pila deben copiarse "cuando se pasa".
Parte de la respuesta antes de la prueba, indicando qué documentos se contradicen con el ejemplo.
Documento sobre la transición a los estados ARC :
Los bloques "simplemente funcionan" cuando pasas bloques arriba de la pila en modo ARC, como en una devolución. Ya no tienes que llamar a
Block_copy
. Aún debe usar[^{} copy]
al pasar "abajo" la pila aarrayWithObjects:
y otros métodos que hacen una retención.
Y docs sobre bloques y propiedades dice:
Debe especificar la
copy
como el atributo de propiedad, ya que un bloque debe copiarse para realizar un seguimiento de su estado capturado fuera del alcance original.