objective-c cocoa automatic-ref-counting objective-c-blocks

objective c - Bloques de cacao como punteros fuertes vs copia



objective-c cocoa (5)

Trabajé varias veces con bloques y con punteros a los que tenía una referencia fuerte.

Escuché que debería usar copiar, pero ¿cuál es la implicación de trabajar con bloques como punteros y no con el objeto en bruto?

Nunca recibí una queja del compilador, que no debería usar

@property (nonatomic, strong) MyBlock block;

pero debe usar

@property (nonatomic, copy) MyBlock block;

que yo sepa, el bloque es solo un objeto, entonces ¿por qué preferir copiar de todos modos?


Nota: 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. Para más información, vea Temas de programación de bloques .


Según tengo entendido, se requiere copy cuando el objeto es mutable. Use esto si necesita el valor del objeto tal como está en este momento, y no desea que ese valor refleje los cambios realizados por otros propietarios del objeto. Deberá liberar el objeto cuando haya terminado con él porque está reteniendo la copia.

Por otro lado, strong significa que posee el objeto hasta que se necesita. Es un reemplazo para el atributo de retain , como parte de ARC.

Fuente: Objective-C declaró atributos de propiedad (no atómicos, copia, fuerte, débil)


Usted está preguntando sobre el modificador de propiedad para una propiedad. Esto afecta al captador y / o establecedor sintetizado (o auto-sintetizado) de la propiedad si se sintetiza (o se sintetiza automáticamente).

La respuesta a esta pregunta será diferente entre MRC y ARC.

  • En MRC, los modificadores de propiedad de propiedad incluyen assign , retain y copy . strong se introdujo con ARC, y cuando strong se usa en MRC, es sinónimo de retain . Entonces, la pregunta sería acerca de la diferencia entre retain y copy , y hay mucha diferencia, porque el establecedor de copy guarda una copia del valor dado.

    Los bloques deben copiarse para usarse fuera del ámbito donde se creó (con un literal de bloque). Dado que su propiedad almacenará el valor como una variable de instancia que persiste en todas las llamadas a funciones, y es posible que alguien le asigne un bloque desocupado desde el ámbito en el que se creó, la convención es que debe copiarlo. copy es el modificador de propiedad correcto.

  • En ARC, strong hace que la variable de instancia subyacente __strong , y copy también la haga __strong y agregue la semántica de copia al configurador. Sin embargo, ARC también garantiza que siempre que se guarde un valor en una variable __strong de tipo bloque-puntero, se realiza una copia. Su propiedad tiene el tipo MyBlock , que supongo que es un typedef para un tipo de puntero de bloque. Por lo tanto, aún se hará una copia en el establecedor si el calificador de propiedad fuera strong . Por lo tanto, en ARC, no hay diferencia entre usar strong y copy para esta propiedad.

Si esta declaración se puede usar tanto en MRC como en ARC (por ejemplo, un encabezado en una biblioteca), sería una buena idea usar la copy para que funcione correctamente en ambos casos.


Respuesta corta

La respuesta es histórica, usted está completamente correcto de que en el código ARC actual no hay necesidad de usar copy y una propiedad segura está bien. Lo mismo ocurre, por ejemplo, con las variables locales y globales.

Respuesta larga

A diferencia de otros objetos, un bloque puede almacenarse en la pila, esta es una optimización de implementación y, como tal, como otras optimizaciones del compilador, no debe tener un impacto directo en el código escrito. Esta optimización beneficia a un caso común en el que se crea un bloque, se pasa como un método / argumento de función, se usa por esa función y luego se descarta: el bloque se puede asignar rápidamente a la pila y luego se puede eliminar sin el montón (grupo de memoria dinámica) estar involucrado.

Compare esto con las variables locales , que (a) creadas en la pila , (b) se destruyen automáticamente cuando la función propietaria / método regresa y (c) puede ser transferida por dirección a métodos / funciones llamadas por la función propietaria. La dirección de una variable local no se puede almacenar y usar después de que su función / método propietario haya regresado, la variable ya no existe.

Sin embargo , se espera que los objetos superen su función / método de creación (si es necesario), por lo que, a diferencia de las variables locales, se asignan en el montón y no se destruyen automáticamente en función de su función / método de creación de retorno, sino más bien en función de si todavía son necesarios, y La "necesidad" aquí está determinada automáticamente por ARC en estos días.

La creación de un bloque en la pila puede optimizar un caso común, pero también causa un problema: si el bloque debe durar más que su creador, como suelen hacerlo los objetos, entonces se debe mover al montón antes de que se destruya la pila de sus creadores.

Cuando se lanzó por primera vez la implementación del bloque, la optimización del almacenamiento de bloques en la pila se hizo visible para los programadores, ya que el compilador en ese momento no podía manejar automáticamente el bloqueo al montón cuando era necesario: los programadores tenían que usar una función block_copy() para hazlo ellos mismos

Si bien este enfoque podría no estar fuera de lugar en el mundo de C de bajo nivel (y los bloques son una construcción de C), tener programadores de Objective-C de alto nivel que manejen manualmente la optimización de un compilador no es realmente bueno. Como Apple lanzó nuevas versiones de las mejoras del compilador donde se hizo. Al principio, a los programadores les dijeron que podían reemplazar block_copy(block) con [block copy] , que encajaba con los objetos normales de Objective-C. Luego, el compilador comenzó a copiar automáticamente los bloques de la pila según fuera necesario, pero esto no siempre fue documentado oficialmente.

No ha habido necesidad de copiar manualmente los bloques de la pila durante un tiempo, aunque Apple no puede ignorar sus orígenes y se refiere a hacerlo como "mejor práctica", lo que sin duda es discutible. En la última versión, septiembre de 2014, de Working with Blocks de Apple, declararon que las propiedades con valores de bloque deberían usar la copy , pero luego se eliminan inmediatamente ( énfasis agregado):

Nota: 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.

No hay necesidad de "mostrar el comportamiento resultante"; almacenar el bloque en la pila en primer lugar es una optimización y debe ser transparente al código. Al igual que en otras optimizaciones del compilador, el código debería obtener el beneficio del rendimiento sin la participación del programador.

Entonces, mientras use ARC y los compiladores de Clang actuales, puede tratar los bloques como otros objetos, y como los bloques son inmutables, eso significa que no necesita copiarlos. Confíe en Apple, incluso si parecen ser nostálgicos por los "viejos tiempos en que hicimos las cosas a mano" y lo aliento a dejar recordatorios históricos en su código, no es necesario copy .

Tu intuición era correcta.

HTH


what is the implication in working with blocks as pointers and not with the raw object?

Nunca está utilizando el valor en bruto, siempre tiene un puntero a un bloque: un bloque es un objeto.

Mirando su ejemplo específico, asumo que usted quiere mantener el bloqueo alrededor, "entonces ¿por qué preferir una copia de todos modos" enter code here ? Bueno, es una cuestión de seguridad (este ejemplo está tomado del blog Mike Ash). Dado que los bloques se asignan en la pila (y no en el montón como el resto de los objetos en objetivo-c), cuando haces algo como esto:

[dictionary setObject: ^{ printf("hey hey/n"); } forKey: key];

Está asignando el bloque en el marco de pila de su ámbito actual, por lo que cuando finaliza el alcance (por ejemplo, al devolver el diccionario), el marco de pila se destruye y el bloque va con él. Así que tienes un puntero colgante. Aconsejaría leer el artículo de Mike completamente. De todos modos, puede ir con una propiedad segura si al asignar el bloque lo copia:

self.block = [^{} copy];

Edit: Después de ver la fecha del artículo de Mike, asumo que este fue el comportamiento Pre-ARC. En ARC parece que funciona como se espera y no se bloquea.

Edit2: Después de experimentar con Non-ARC no se bloquea también. Pero este ejemplo muestra diferentes resultados dependiendo del uso de ARC o no:

void (^block[10])(); int i = -1; while(++i < 10) block[i] = ^{ printf("%d/n", i); }; for(i = 0; i < 10; i++) block[i]();

Citando a Mike Ashe sobre los diferentes resultados:

La razón por la que se imprimen diez 9s en el primer caso es bastante simple: el bloque que se crea dentro del bucle tiene una vida útil que está ligada al alcance interno del bucle. El bloque se destruye en la siguiente iteración del bucle, y al salir del bucle. Por supuesto, "destruir" solo significa que su ranura en la pila está disponible para ser sobrescrita. Simplemente sucede que el compilador reutiliza la misma ranura cada vez que pasa por el bucle, por lo que al final, la matriz se llena con punteros idénticos y, por lo tanto, se obtiene un comportamiento idéntico.