Manejo de problemas de propiedad de puntero a puntero en ARC

Supongamos que el objeto A tiene una propiedad:

@property (nonatomic, strong) Foo * bar; 

Sintetizado en la implementación como:

 @synthesize bar = _bar; 

El Objeto B manipula un Foo ** , como en esta llamada de ejemplo del Objeto A :

 Foo * temp = self.bar; [objB doSomething:&temp]; self.bar = temp; 
  • ¿Puede esto, o algo similar, hacerse legítimamente?
  • ¿Cuál es la statement correcta para el método doSomething: 😕

Además, supongamos que el Objeto B puede desasignarse antes de que tenga la oportunidad de establecer la propiedad de la bar (y así asumir la propiedad de la instancia señalada por la temp ). ¿Cómo le diría a ARC que me entregue una referencia propietaria? En otras palabras, si quisiera que el siguiente fragmento de ejemplo funcione, ¿cómo debería manejar los problemas de ARC?

 Foo * temp = self.bar; // Give it a reference to some current value [objB doSomething:&temp]; // Let it modify the reference self.bar = nil; // Basically release whatever we have _bar = temp; // Since we're getting back an owning reference, bypass setter 
  • ¿En qué estoy pensando?

EDITAR

Basado en la respuesta de @KevinBallard, solo quiero confirmar mi comprensión. ¿Es esto correcto?

Objeto A:

 @implementation ObjectA @synthesize bar = _bar; - (void)someMethod { ObjectB * objB = [[ObjectB alloc] initWithFoo:&_bar]; // objB handed off somewhere and eventually it's "doSomething" method is called. } @end 

Objeto B:

 @implementation ObjectB { Foo * __autoreleasing * _temp; } - (id)initWithFoo:(Foo * __autoreleasing *)temp { id self = [super init]; if (self) { _temp = temp; } return self; } - (void)doSomething { ... *_temp = [[Foo alloc] init]; ... } @end 

Esto crea un error en tiempo de comstackción: passing address of non-local object to __autoreleasing parameter for write-back

ARC necesita conocer la propiedad de una referencia de objeto para poder determinar cuándo liberarla, etc. Para cualquier variable (local, de instancia o global), ARC tiene reglas para determinar la propiedad; ya sea por inferencia o por un atributo explícito. Esto equivale a la necesidad previa del ARC para que el progtwigdor rastree la propiedad.

Pero, ¿qué sucede si tienes una referencia a una variable? Usted no podía (antes de ARC) escribir código que aceptara una referencia a una variable y que siempre funcionaría correctamente independientemente de la propiedad de esa variable, ya que no podía saber si necesitaba liberar etc. Es decir, no puede construir código que funciona para una propiedad desconocida (¡en el sentido de cambiar!).

ARC se enfrenta al mismo problema y su solución es inferir, o aceptar un atributo explícito que especifique, la propiedad de la variable referenciada y luego requerir que la persona que llama haga arreglos para que se pase una referencia a una variable de apropiación apropiada. Este último bit puede requerir el uso de variables temporales ocultas. Esto se denomina “solución menos mala” en la especificación y se denomina “pass-by-writeback”.

La primera parte de la pregunta:

 Foo * temp = self.bar; [objB doSomething:&temp]; self.bar = temp; 
  • ¿Puede esto, o algo similar, hacerse legítimamente?

Sí, el código está bien por ARC. Se infiere que la temp es strong y algunas cosas detrás de escena pasa para pasarlo por referencia a hacer algo.

  • ¿Cuál es la statement correcta para el método doSomething:?
 - (void) doSomething:(Foo **)byRefFoo 

ARC deduce por byRefFoo ser del tipo Foo * __autoreleasing * – una referencia a una referencia de autorrelleno. Esto es lo que requiere el “pass-by-writeback”.

Este código solo es válido porque la temp es local. Sería incorrecto hacer esto con una variable de instancia (como descubrió en su EDIT). También solo es válido suponiendo que el parámetro se está utilizando en el modo “salida” estándar y se ha asignado cualquier valor actualizado cuando doSomething: regresa . Ambas cosas se deben a que la manera de pasar por escrito funciona como parte de la “solución menos mala” …

Resumen : cuando se usan variables locales, se pueden pasar por referencia para usar en el patrón estándar de “salida” con ARC deduciendo cualquier atributo requerido, etc.

Bajo el capó

En lugar del Foo de la pregunta, usaremos un tipo Breadcrumbs ; esto es esencialmente un NSString envuelto que rastrea cada init , retain , release , release autorelease y dealloc (bueno casi como verás a continuación) para que podamos ver lo que está sucediendo. Cómo se escribe Breadcrumbs no es material.

Ahora considere la siguiente clase:

 @implementation ByRef { Breadcrumbs *instance; // __strong inferred } 

Un método para cambiar un valor pasado por referencia:

 - (void) indirect:(Breadcrumbs **)byRef // __autoreleasing inferred { *byRef = [Breadcrumbs newWith:@"banana"]; } 

Un contenedor simple para indirect: para que podamos ver lo que se pasa y cuando vuelve:

 - (void) indirectWrapper:(Breadcrumbs **)byRef // __autoreleasing inferred { NSLog(@"indirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]); [self indirect:byRef]; NSLog(@"indirect: returned"); } 

Y un método para demostrar indirect: llamado a una variable local (llamada imaginativamente local ):

 - (void) demo1 { NSLog(@"Strong local passed by autoreleasing reference"); Breadcrumbs *local; // __strong inferred local = [Breadcrumbs newWith:@"apple"]; NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]); [self indirectWrapper:&local]; NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]); } @end 

Ahora, algunos códigos para ejercitar demo1 localizando el grupo de autorrelease para que podamos ver qué se asigna, libera y cuándo:

 ByRef *test = [ByRef new]; NSLog(@"Start demo1"); @autoreleasepool { [test demo1]; NSLog(@"Flush demo1"); } NSLog(@"End demo1"); 

Ejecutar lo anterior produce lo siguiente en la consola:

 ark[2041:707] Start demo1 ark[2041:707] Strong local passed by autoreleasing reference ark[2041:707] >>> 0x100176f30: init ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1 ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1 ark[2041:707] >>> 0x100427d10: init ark[2041:707] >>> 0x100427d10: autorelease ark[2041:707] indirect: returned ark[2041:707] >>> 0x100427d10: retain ark[2041:707] >>> 0x100176f30: release ark[2041:707] >>> 0x100176f30: dealloc ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2 ark[2041:707] >>> 0x100427d10: release ark[2041:707] Flush demo1 ark[2041:707] >>> 0x100427d10: release ark[2041:707] >>> 0x100427d10: dealloc ark[2041:707] End demo1 

[Las líneas “>>>” vienen de Breadcrumbs .] Simplemente sigue las direcciones de los objetos (0x100 …) y las variables (0x7fff …) y todo está claro …

Bueno, tal vez no! Aquí está de nuevo con comentarios después de cada fragmento:

 ark[2041:707] Start demo1 ark[2041:707] Strong local passed by autoreleasing reference ark[2041:707] >>> 0x100176f30: init ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1 

Aquí vemos que [Breadcrumbs newWith:@"apple"] crea un objeto en la dirección 0x100176f30 . Esto se almacena en local , cuya dirección es 0x7fff5fbfedc0 , y el objeto tiene 1 propietario ( local ).

 ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1 

Aquí viene la variable oculta: como indirect: requiere una referencia a una variable de autorrelleno ARC ha creado una nueva variable, cuya dirección es 0x7fff5fbfedb8 , y ha copiado la referencia del objeto ( 0x100176f30 ) en eso.

 ark[2041:707] >>> 0x100427d10: init ark[2041:707] >>> 0x100427d10: autorelease ark[2041:707] indirect: returned 

Dentro de indirect: se crea un nuevo objeto y ARC lo libera automáticamente antes de asignarlo, porque las referencias aprobadas se refieren a una variable de autorrelleno.

Nota: ARC no necesita hacer nada con los contenidos anteriores ( 0x100176f30 ) de la variable referenciada ( 0x7fff5fbfedb8 ) ya que se libera automáticamente y, por lo tanto, no es su responsabilidad. Es decir, lo que significa “autorrealización de la propiedad” es que cualquier referencia asignada debe haberse liberado automáticamente. Verá que al crear la variable oculta, ARC no retenía ni liberaba automáticamente su contenido; no era necesario que lo hiciera, ya que sabe que hay una referencia fuerte (en local ) al objeto que está gestionando. [En el último ejemplo debajo de ARC no se elimina el retener / liberar automáticamente.]

 ark[2041:707] >>> 0x100427d10: retain ark[2041:707] >>> 0x100176f30: release ark[2041:707] >>> 0x100176f30: dealloc 

Estas acciones resultan de copiar (el “writeback” en call-by-writeback) el valor de la variable oculta en local . El release / dealloc es para la antigua referencia fuerte en local , y la retención es para el objeto al que hace referencia la variable oculta (que fue autorrellezada por indirect: 🙂

Nota: esta escritura retrospectiva es la razón por la cual esto solo funciona para el patrón de “salida” de uso de referencia de pasada indirect: no puede almacenar la referencia pasada a indirect: como ocurre con una variable local oculta que está a punto de desaparecer …

 ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2 

Entonces, después de la llamada local refiere al nuevo objeto, y tiene 2 propietarios: cuentas local para una, y la otra es la autorelease en forma indirect:

 ark[2041:707] >>> 0x100427d10: release 

demo1 ahora ha finalizado, por lo que ARC libera el objeto en local

 ark[2041:707] Flush demo1 ark[2041:707] >>> 0x100427d10: release ark[2041:707] >>> 0x100427d10: dealloc ark[2041:707] End demo1 

y luego de demo1 el @autoreleasepool localizado maneja la liberación automática pendiente de indirect: ahora la propiedad es cero y obtenemos el dealloc .

Pasar variables de instancia por referencia

Lo anterior trata de pasar variables locales por referencia, pero desafortunadamente el pass-by-writeback no funciona para las variables de instancia. Hay dos soluciones básicas:

  • copie su variable de instancia a un local

  • agregar algunos atributos

Para demostrar el segundo, agregamos a la clase ByRef un strongIndirect: que especifica que requiere una referencia a una variable fuerte:

 - (void) strongIndirect:(Breadcrumbs * __strong *)byRef { *byRef = [Breadcrumbs newWith:@"plum"]; } - (void) strongIndirectWrapper:(Breadcrumbs * __strong *)byRef { NSLog(@"strongIndirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]); [self strongIndirect:byRef]; NSLog(@"strongIndirect: returned"); } 

y una demo2 correspondiente que usa la variable de instancia de ByRef (de nuevo con el nombre imaginativo de la instance ):

 - (void) demo2 { NSLog(@"Strong instance passed by strong reference"); instance = [Breadcrumbs newWith:@"orange"]; NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]); [self strongIndirectWrapper:&instance]; NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]); } 

Ejecute esto con un código similar al de la demo1 arriba y obtenemos:

 1 ark[2041:707] Start demo2 2 ark[2041:707] Strong instance passed by strong reference 3 ark[2041:707] >>> 0x100176f30: init 4 ark[2041:707] instance: addr 0x100147518, contains 0x100176f30 - orange, owners 1 5 ark[2041:707] strongIndirect: passed reference 0x100147518, contains 0x100176f30 - orange, owners 1 6 ark[2041:707] >>> 0x100427d10: init 7 ark[2041:707] >>> 0x100176f30: release 8 ark[2041:707] >>> 0x100176f30: dealloc 9 ark[2041:707] strongIndirect: returned 10 ark[2041:707] instance: addr 0x100147518, contains 0x100427d10 - plum, owners 1 11 ark[2041:707] Flush demo2 12 ark[2041:707] End demo2 

Que es un poco más corto que antes. Esto es por dos razones:

  • Como estamos pasando una variable fuerte ( instance ) a un método ( strongIndirect: que espera una referencia a una variable fuerte, no es necesario que ARC use una variable oculta; las variables en la línea 4 y 5 son las mismas ( 0x100147518 )

  • Como ARC conoce la variable referenciada en strongIndirect: es sólida, no es necesario almacenar una referencia autorrellenada dentro de strongIndirect: y luego escribirla de nuevo después de la llamada: ARC solo hace una asignación fuerte estándar, líneas 6-8, y no hay nada que liberación automática más tarde (entre las líneas 11 y 12).

Does strongIndirect: trabaja para locales fuertes?

Por supuesto, aquí está demo3 :

 - (void) demo3 { NSLog(@"Strong local passed by strong reference"); Breadcrumbs *local; // __strong inferred local = [Breadcrumbs newWith:@"apple"]; NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]); [self strongIndirectWrapper:&local]; NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]); } 

Ejecutar esto con nuestro envoltorio estándar produce:

 1 ark[2041:707] Start demo3 2 ark[2041:707] Strong local passed by strong reference 3 ark[2041:707] >>> 0x100176f30: init 4 ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1 5 ark[2041:707] strongIndirect: passed reference 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1 6 ark[2041:707] >>> 0x100427d20: init 7 ark[2041:707] >>> 0x100176f30: release 8 ark[2041:707] >>> 0x100176f30: dealloc 9 ark[2041:707] strongIndirect: returned 10 ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d20 - plum, owners 1 11 ark[2041:707] >>> 0x100427d20: release 12 ark[2041:707] >>> 0x100427d20: dealloc 13 ark[2041:707] Flush demo3 14 ark[2041:707] End demo3 

Esto es casi lo mismo que el ejemplo anterior, solo dos diferencias menores:

  • Se pasa la dirección del local en la stack ( 0x7fff5fbfedc0 ), líneas 4 y 5

  • Como está almacenado en un local, el ARC limpia el nuevo objeto, líneas 11 y 12.

¿Por qué no siempre agrega __strong a los argumentos de referencia?

¡Una razón es porque no todo es fuerte! Los trabajos de reescritura de ARC también funcionan para locales débiles. Nuestra demostración final:

 - (void) demo4 { NSLog(@"Weak instance passed by autoreleasing reference"); instance = [Breadcrumbs newWith:@"peach"]; Breadcrumbs __weak *weakLocal = instance; NSLog(@"weakLocal: addr %p, contains %p - %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]); [self indirectWrapper:&weakLocal]; NSLog(@"weakLocal: addr %p, contains %p -, %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]); } 

[Aquí acabamos de usar la instance así que tenemos algo para hacer una referencia débil a.]

Ejecutar esto con nuestro envoltorio estándar produce:

 1 ark[2041:707] Start demo4 2 ark[2041:707] Weak instance passed by autoreleasing reference 3 ark[2041:707] >>> 0x100427d20: init 4 ark[2041:707] >>> 0x100427d10: release 5 ark[2041:707] >>> 0x100427d10: dealloc 6 ark[2041:707] weakLocal: addr 0x7fff5fbfedd0, contains 0x100427d20 - peach, owners 1 7 ark[2041:707] >>> 0x100427d20: autorelease 8 ark[2041:707] indirect: passed reference 0x7fff5fbfedc8, contains 0x100427d20 - peach, owners 2 9 ark[2041:707] >>> 0x100429040: init 10 ark[2041:707] >>> 0x100429040: autorelease 11 ark[2041:707] indirect: returned 12 ark[2041:707] weakLocal: addr 0x7fff5fbfedd0, contains 0x100429040 -, banana, owners 1 13 ark[2041:707] Flush demo4 14 ark[2041:707] >>> 0x100429040: release 15 ark[2041:707] >>> 0x100429040: dealloc 16 ark[2041:707] >>> 0x100427d20: release 17 ark[2041:707] End demo4 

Notas:

  • Las líneas 3-5 solo están configurando una instance , crean un nuevo valor y lanzan el anterior, el material real comienza en la línea 6

  • ARC usa una variable oculta (línea 8, 0x7fff5fbfedc8 ) para locales débiles (línea 6, 0x7fff5fbfedd0 ) también

  • ARC no ha eliminado la retención / liberación automática al asignar a esta variable oculta como lo hizo anteriormente. Puede ver la liberación automática en la línea 7, pero mi ruta de navegación omitió la retain , pero la propiedad de 2 en la línea 8 muestra que ocurrió.

  • Hay dos autorreleases, por lo que debe haber dos versiones correspondientes cuando se vacía el grupo (líneas 14 y 16); solo hay un dealloc correspondiente (línea 15) ya que el otro objeto ( 0x100427d20 ) se referencia por instance y ARC lo limpia cuando nuestra instancia de ByRef desaparece.

Resumen

  • Sin ningún atributo agregado, ARC hará lo correcto para variables locales (inferidas fuertes) pasadas como parámetros por referencia (autorelleno inferido). (Y “local” incluye parámetros para el método actual).

  • Esto es implementado por ARC usando pass-by-writeback y solo funciona si usted sigue el patrón de parámetro “out”. Si desea almacenar la referencia aprobada para utilizarla más adelante, deberá hacer más usted mismo.

  • Si desea pasar variables de instancia por referencia, debe copiarlas en locals o atribuir el tipo de parámetro de recepción con __strong .

  • pass-by-writeback también funciona para __weak locales.

Espero que ayude.


Adición Apr 2016: __block variables

En los comentarios que Heath Borders ha pedido:

¿Qué pasa si mi variable local es un tipo de __block ? Estoy bastante seguro de que este caso es igual que una variable de instancia en el sentido de que necesito copiarlos a los locales, o atribuir el tipo de parámetro de recepción con __strong , pero tengo curiosidad acerca de la opinión de otra persona.

Interesante pregunta.

La especificación declara:

El pass-by-writeback está mal formado si la expresión del argumento no tiene una forma legal:

&var , donde var es una variable escalar de duración de almacenamiento automático con tipo de puntero de objeto retenible

Las variables locales en (Objective-) C de forma predeterminada tienen una duración de almacenamiento automática : se crean y destruyen automáticamente a medida que se ingresa / sale su función / método / bloque adjunto. En la respuesta anterior, cuando nos referimos a “variable local”, nos referimos implícitamente a variables locales con duración de almacenamiento automática.

Las variables locales se pueden declarar con un calificador de almacenamiento o un especificador de clase de almacenamiento para cambiar la duración de almacenamiento de la variable. El más visto es static ; Las variables locales con duración de almacenamiento estática existen a lo largo de la ejecución del progtwig, pero solo son (directamente) accesibles dentro de su scope local.

Si intenta pasar una variable local static con pass-by-writeback, el comstackdor producirá un error que indica que la variable no tiene una duración de almacenamiento automática. Debe manejar dichas variables de la misma manera que las variables de instancia (que han asignado la duración de almacenamiento ).

El __block almacenamiento __block se introdujo en (Objective-) C como parte de bloques y la especificación establece:

El __block almacenamiento __block es mutuamente exclusivo de los calificadores de almacenamiento local existentes auto , register y static . Las variables calificadas por __block actúan como si estuvieran en almacenamiento asignado y este almacenamiento se recupera automáticamente después del último uso de dicha variable.

Por lo tanto, una variable local __block actúa como si tuviera una duración de almacenamiento asignada, al igual que las variables de instancia, y por lo tanto, mediante la especificación de pass-by-writeback, dicha variable no puede utilizarse ya que no tiene una duración de almacenamiento automática …

Sin embargo, con las herramientas actuales al momento de escribir (Xcode 7.2, Clang 7.0.2) __block las variables locales calificadas con __block son compatibles con el pass-by-writeback y son manejadas de la misma manera que aquellas con duración de almacenamiento automático __autoreleasing se usa un __autoreleasing temporary oculto.

Esto parece ser indocumentado.

Habiendo dicho que es “seguro” usarlo en el sentido de que comstackrá o no, y una vez comstackdo, el código funcionará incluso si las herramientas cambian y no se puede volver a comstackr en el futuro … (al menos sin manejo) la variable es la misma que las variables de instancia deben ser manejadas).

La razón por la cual puede ser aceptada se puede deducir del razonamiento de las restricciones sobre el rechazo de escritura (énfasis agregado):

Razón fundamental

La restricción en la forma del argumento tiene dos propósitos. En primer lugar, hace que sea imposible pasar la dirección de una matriz al argumento, lo que sirve para proteger contra un riesgo, por lo demás grave, de infringir erróneamente un argumento de “matriz” como un parámetro fuera. En segundo lugar, hace que sea mucho menos probable que el usuario vea problemas de alias confusos debido a la implementación, a continuación, donde su almacenamiento temporal no se ve inmediatamente en la variable de argumento original.

No hay ninguna razón técnica por la que las variables de instancia no puedan ser soportadas por el pass-by-writeback, pero podría ser confuso debido a aliasing. __block variables de __block encuentran en algún lugar entre las automáticas y las asignadas, por lo que es posible que los autores de las herramientas actuales opten por agruparlas con las primeras en lugar de las últimas para pasarlas por escrito.

Nota: Los lectores familiarizados con la implementación de bloques sabrán que un local calificado con __block se puede implementar como una optimización con duración de almacenamiento automática o asignada, dependiendo del uso, y por lo tanto se preguntan si esto afecta su uso para el rechazo. Esto no parece ser el caso.

Esto es perfectamente legítimo. El acceso a la propiedad es irrelevante; pasar un puntero a un objeto se hace comúnmente con objetos NSError* .

La forma correcta de declarar su método es

 - (returntype)doSomething:(Foo * __autoreleasing *)arg; 

Esto lo declara como un puntero a un objeto __autoreleasing , lo que básicamente significa que se supone que el objeto al que se apunta ha sido -autorelease d.

En cuanto al “Más”, eso no es un problema bajo ARC. Tu linea

 Foo * temp = self.bar; 

es equivalente a

 __strong Foo *temp = self.bar; 

lo cual espero que sea obvio para usted, ya que esto hace que la referencia temp sea ​​una referencia fuerte y, por lo tanto, “posee” su valor mientras exista la variable. En otras palabras, puedes decir

 Foo *temp = self.bar; self.bar = nil; 

y la temp sigue siendo válida.