¿En qué situaciones debemos escribir el __calificador de propiedad de arrendamiento automático bajo ARC?

118

Estoy intentando completar el rompecabezas.

__stronges el valor predeterminado para todos los punteros de objetos retenibles de Objective-C como NSObject, NSString, etc. Es una referencia sólida. ARC lo equilibra con un -releaseal final del alcance.

__unsafe_unretainedes igual a la forma antigua. Se usa para un puntero débil sin retener el objeto retenible.

__weakes como __unsafe_unretainedexcepto que es una referencia débil de puesta a cero automática, lo que significa que el puntero se establecerá en cero tan pronto como se desasigne el objeto referenciado. Esto elimina el peligro de punteros colgantes y errores EXC_BAD_ACCESS.

Pero, ¿para qué sirve exactamente __autoreleasing? Me cuesta encontrar ejemplos prácticos sobre cuándo necesito usar este calificador. Creo que es solo para funciones y métodos que esperan un puntero-puntero como:

- (BOOL)save:(NSError**);

o

NSError *error = nil;
[database save:&error];

que bajo ARC debe declararse de esta manera:

- (BOOL)save:(NSError* __autoreleasing *);

Pero esto es demasiado vago y me gustaría entender completamente por qué . Los fragmentos de código que encuentro colocan el __autoreleasing entre las dos estrellas, lo que me parece extraño. El tipo es NSError**(un puntero-puntero a NSError), entonces, ¿por qué colocarlo __autoreleasingentre las estrellas y no simplemente frente a ellas NSError**?

Además, puede haber otras situaciones en las que deba confiar __autoreleasing.

Miembro orgulloso
fuente
1
Tengo la misma pregunta y las respuestas a continuación no son totalmente convincentes ... por ejemplo, ¿por qué el sistema no proporciona interfaces que toman argumentos NSError ** declarados con el decorador __autoreleasing como usted y las notas de la versión de Transitioning to Arc dicen que ¿debiera ser? por ejemplo, ¿Alguna de las muchas de estas rutinas en NSFileManager.h?
Papá

Respuestas:

67

Tienes razón. Como explica la documentación oficial:

__autoreleasing para denotar argumentos que se pasan por referencia (id *) y se liberan automáticamente al devolver.

Todo esto está muy bien explicado en la guía de transición ARC .

En su ejemplo de NSError, la declaración significa __strong, implícitamente:

NSError * e = nil;

Se transformará en:

NSError * __strong error = nil;

Cuando llamas a tu savemétodo:

- ( BOOL )save: ( NSError * __autoreleasing * );

El compilador tendrá que crear una variable temporal, establecida en __autoreleasing. Entonces:

NSError * error = nil;
[ database save: &error ];

Se transformará en:

NSError * __strong error = nil;
NSError * __autoreleasing tmpError = error;
[ database save: &tmpError ];
error = tmpError;

Puede evitar esto declarando el objeto de error como __autoreleasing, directamente.

Macmade
fuente
3
No, __autoreleasingsolo se usa para argumentos pasados ​​por referencia. Este es un caso especial, ya que tiene un puntero al puntero de un objeto. Ese no es el caso con cosas como los constructores de conveniencia, ya que solo devuelven un puntero a un objeto y ARC lo maneja automáticamente.
Macmade
7
¿Por qué el calificador __autoreleasing se coloca entre las estrellas y no solo frente a NSError **? Esto me parece extraño ya que el tipo es NSError **. ¿O es porque esto está tratando de indicar que el puntero NSError * apuntado tiene que ser calificado como apuntando a un objeto liberado automáticamente?
Miembro orgulloso
1
@Proud Member con respecto a su primer comentario, que es incorrecto (si lo entiendo correctamente), vea la respuesta de Glen Low a continuación. El objeto de error se crea y se asigna a una variable de liberación automática (la que ingresó) dentro de la función de guardar. Esta asignación hace que el objeto se retenga y se libere automáticamente en ese momento. La declaración de la función de guardar nos impide enviarle algo que no sea una variable de liberación automática porque eso es lo que necesita, razón por la cual el compilador crea una variable temporal si lo intentamos.
Colin
2
Entonces, ¿por qué ninguna de las interfaces de Apple parece tener esto? por ejemplo, todo en NSFileManager.h?
Papá
1
@Macmade: Por casualidad, noté que su respuesta ha sido editada (por stackoverflow.com/users/12652/comptrol ) y tengo la impresión de que al menos los cambios en su primer ejemplo ("implícitamente ... se transformarán en ...) están equivocados, porque el calificador __ strong se ha movido de la segunda línea a la primera línea. Quizás pueda verificar eso.
Martin R
34

Continuando con la respuesta de Macmade y la pregunta de seguimiento de Proud Member en los comentarios (también habría publicado esto como comentario, pero supera el número máximo de caracteres):

He aquí por qué el calificador variable de __autoreleasing se coloca entre las dos estrellas.

Para comenzar, la sintaxis correcta para declarar un puntero de objeto con un calificador es:

NSError * __qualifier someError;

El compilador perdonará esto:

__qualifier NSError *someError;

pero no es correcto. Consulte la guía de transición de Apple ARC (lea la sección que comienza "Debería decorar las variables correctamente ...").

Para abordar la pregunta en cuestión: un puntero doble no puede tener un calificador de gestión de memoria ARC porque un puntero que apunta a una dirección de memoria es un puntero a un tipo primitivo, no un puntero a un objeto. Sin embargo, cuando declara un puntero doble, ARC quiere saber cuáles son las reglas de administración de memoria para el segundo puntero. Es por eso que las variables de doble puntero se especifican como:

SomeClass * __qualifier *someVariable;

Entonces, en el caso de un argumento de método que es un puntero doble NSError, el tipo de datos se declara como:

- (BOOL)save:(NSError* __autoreleasing *)errorPointer;

que en inglés dice "puntero a un puntero de objeto NSError de liberación automática".

Binyamin Bauman
fuente
15

La especificación definitiva de ARC dice que

Para objetos de liberación automática, el nuevo puntero se retiene, libera automáticamente y se almacena en lvalue usando semántica primitiva.

Entonces, por ejemplo, el código

NSError* __autoreleasing error = someError;

en realidad se convierte en

NSError* error = [[someError retain] autorelease];

... razón por la cual funciona cuando tiene un parámetro NSError* __autoreleasing * errorPointer, el método llamado asignará el error *errorPointery la semántica anterior se activará.

Puede usarlo __autoreleasingen un contexto diferente para forzar un objeto ARC en el grupo de liberación automática, pero eso no es muy útil ya que ARC solo parece usar el grupo de liberación automática en el retorno del método y ya lo maneja automáticamente.

Glen Low
fuente