Dans quelles situations devons-nous écrire le qualificatif de propriété __autoreleasing sous ARC?

118

J'essaye de terminer le puzzle.

__strongest la valeur par défaut pour tous les pointeurs d'objet conservables Objective-C comme NSObject, NSString, etc. C'est une référence forte. ARC équilibre avec un -releaseà la fin de la portée.

__unsafe_unretainedéquivaut à l'ancienne. Il est utilisé pour un pointeur faible sans conserver l'objet pouvant être conservé.

__weakest comme __unsafe_unretainedsauf que c'est une référence faible à mise à zéro automatique, ce qui signifie que le pointeur sera mis à nil dès que l'objet référencé sera libéré. Cela élimine le risque de pointeurs suspendus et d'erreurs EXC_BAD_ACCESS.

Mais à quoi sert exactement __autoreleasing? J'ai du mal à trouver des exemples pratiques sur le moment où je dois utiliser ce qualificatif. Je pense que ce n'est que pour les fonctions et les méthodes qui attendent un pointeur-pointeur tel que:

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

ou

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

qui sous ARC doit être déclaré de cette façon:

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

Mais c'est trop vague et j'aimerais bien comprendre pourquoi . Les extraits de code que je trouve placent le __autoreleasing entre les deux étoiles, ce qui me semble bizarre. Le type est NSError**(un pointeur-pointeur vers NSError), alors pourquoi placer __autoreleasingentre les étoiles et pas simplement devant NSError**?

De plus, il peut y avoir d'autres situations sur lesquelles je dois me fier __autoreleasing.

Membre fier
la source
1
J'ai cette même question et les réponses ci-dessous ne sont pas totalement convaincantes ... par exemple, pourquoi les interfaces fournies par le système qui acceptent les arguments NSError ** ne sont-elles pas déclarées avec le décorateur __autoreleasing comme vous et les notes de publication de Transitioning to Arc disent qu'elles devrait être? par exemple, l'une des nombreuses de ces routines dans NSFileManager.h ??
Papa

Réponses:

67

Vous avez raison. Comme l'explique la documentation officielle:

__autoreleasing pour désigner les arguments qui sont passés par référence (id *) et sont automatiquement libérés au retour.

Tout cela est très bien expliqué dans le guide de transition ARC .

Dans votre exemple NSError, la déclaration signifie __strong, implicitement:

NSError * e = nil;

Sera transformé en:

NSError * __strong error = nil;

Lorsque vous appelez votre saveméthode:

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

Le compilateur devra alors créer une variable temporaire, définie à __autoreleasing. Alors:

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

Sera transformé en:

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

Vous pouvez éviter cela en déclarant __autoreleasingdirectement l' objet d'erreur .

Macmade
la source
3
Non, __autoreleasingn'est utilisé que pour les arguments passés par référence. C'est un cas particulier, car vous avez un pointeur vers le pointeur d'un objet. Ce n'est pas le cas avec des éléments tels que les constructeurs de commodité, car ils ne renvoient qu'un pointeur vers un objet, et comme ARC le gère automatiquement.
Macmade
7
Pourquoi le qualificatif __autoreleasing est-il placé entre les étoiles, et pas seulement devant NSError **? Cela me semble étrange car le type est NSError **. Ou est-ce parce que cela tente d'indiquer que le pointeur NSError * pointé doit être qualifié comme pointant vers un objet libéré automatiquement?
Membre fier
1
@Proud Member en ce qui concerne votre premier commentaire - c'est incorrect (si je vous comprends bien) - voir la réponse de Glen Low ci-dessous. L'objet d'erreur est créé et affecté à une variable de libération automatique (celle que vous avez transmise) dans la fonction de sauvegarde. Cette affectation entraîne la conservation et la libération automatique de l'objet à ce moment-là. La déclaration de la fonction de sauvegarde nous empêche de lui envoyer autre chose qu'une variable de libération automatique car c'est ce dont elle a besoin - c'est pourquoi le compilateur crée une variable temporaire si nous essayons.
Colin
2
Alors pourquoi aucune des interfaces Apple ne semble avoir cela? par exemple, tout dans NSFileManager.h?
Papa
1
@Macmade: Juste par hasard j'ai remarqué que votre réponse a été éditée (par stackoverflow.com/users/12652/comptrol ) et j'ai l'impression qu'au moins les changements apportés à votre premier exemple ("implicitement ... seront transformés en ...) sont faux, parce que le qualificatif __strong a été déplacé de la deuxième ligne à la première ligne. Vous pourriez peut-être vérifier cela.
Martin R
34

Suite à la réponse de Macmade et à la question de suivi du membre fier dans les commentaires, (l'aurait également posté sous forme de commentaire mais cela dépasse le nombre maximal de caractères):

Voici pourquoi le qualificatif variable de __autoreleasing est placé entre les deux étoiles.

Pour commencer, la syntaxe correcte pour déclarer un pointeur d'objet avec un qualificatif est:

NSError * __qualifier someError;

Le compilateur vous pardonnera ceci:

__qualifier NSError *someError;

mais ce n'est pas correct. Consultez le guide de transition Apple ARC (lisez la section qui commence «Vous devez décorer correctement les variables ...»).

Pour répondre à la question en cours: un double pointeur ne peut pas avoir de qualificatif de gestion de mémoire ARC car un pointeur qui pointe vers une adresse mémoire est un pointeur vers un type primitif, pas un pointeur vers un objet. Cependant, lorsque vous déclarez un double pointeur, ARC souhaite savoir quelles sont les règles de gestion de la mémoire pour le deuxième pointeur. C'est pourquoi les variables de pointeur double sont spécifiées comme suit:

SomeClass * __qualifier *someVariable;

Ainsi, dans le cas d'un argument de méthode qui est un double pointeur NSError, le type de données est déclaré comme:

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

qui en anglais dit "pointeur vers un pointeur d'objet NSError __autoreleasing".

Binyamin Bauman
la source
15

La spécification ARC définitive dit que

Pour les objets __autoreleasing, le nouveau pointeur est conservé, libéré automatiquement et stocké dans la valeur l à l'aide de la sémantique primitive.

Par exemple, le code

NSError* __autoreleasing error = someError;

est réellement converti en

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

... c'est pourquoi cela fonctionne lorsque vous avez un paramètre NSError* __autoreleasing * errorPointer, la méthode appelée affectera alors l'erreur à *errorPointeret la sémantique ci-dessus entrera en vigueur.

Vous pouvez utiliser __autoreleasingdans un contexte différent pour forcer un objet ARC dans le pool de libération automatique, mais ce n'est pas très utile car ARC semble uniquement utiliser le pool de libération automatique au retour de méthode et le gère déjà automatiquement.

Glen Low
la source