34 votes

Pourquoi le migrateur ARC indique-t-il que le paramètre -setArgument: de NSInvocation n'est dangereux que si l'argument est __unsafe_unretained?

J'ai été à la migration d'un bloc de code automatique de comptage de référence (ARC), et a l'ARC migrator jeter l'erreur

NSInvocation de setArgument n'est pas sûr pour être utilisé avec un objet avec la propriété autres que les __dangereuse_consignes

sur de code où j'avais attribué un objet en utilisant quelque chose comme

NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:@"1.0"];

puis le définir comme un NSInvocation argument à l'aide de

[theInvocation setArgument:&testNumber1 atIndex:2];

Pourquoi est-il vous empêche de le faire? Il semble juste comme de la mauvaise utilisation de __unsafe_unretained objets comme arguments. Par exemple, le code suivant provoque un accident en vertu de l'ARC:

NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:@"1.0"];
NSMutableArray *testArray = [[NSMutableArray alloc] init];

__unsafe_unretained NSDecimalNumber *tempNumber = testNumber1;

NSLog(@"Array count before invocation: %ld", [testArray count]);
//    [testArray addObject:testNumber1];    
SEL theSelector = @selector(addObject:);
NSMethodSignature *sig = [testArray methodSignatureForSelector:theSelector];
NSInvocation *theInvocation = [NSInvocation invocationWithMethodSignature:sig];
[theInvocation setTarget:testArray];
[theInvocation setSelector:theSelector];
[theInvocation setArgument:&tempNumber atIndex:2];
//        [theInvocation retainArguments];

// Let's say we don't use this invocation until after the original pointer is gone
testNumber1 = nil;

[theInvocation invoke];
theInvocation = nil;

NSLog(@"Array count after invocation: %ld", [testArray count]);
testArray = nil;

en raison de la overrelease d' testNumber1, parce que le temporaire __unsafe_unretained tempNumber variable n'est pas la conserver après l'original pointeur est défini à l' nil (simulation d'un cas où l'invocation est utilisé après la référence initiale à un argument a disparu). Si l' -retainArguments ligne est sans commentaire (provoquant l'NSInvocation pour tenir à l'argument), ce code ne plante pas.

Exactement le même incident se produit si j'utilise testNumber1 directement comme argument d' -setArgument:, et il est également résolu si vous utilisez -retainArguments. Pourquoi, alors, ne l'ARC migrator dire que l'utilisation fermement pointeur comme un argument de NSInvocation de l' -setArgument: est dangereux, sauf si vous utilisez quelque chose, c'est - __unsafe_unretained?

9voto

Chris Devereux Points 3536

C'est une supposition, mais pourrait-il être quelque chose à voir avec l'argument étant passé par référence en tant que void*?

Dans le cas que vous avez mentionné, cela ne semble pas vraiment un problème, mais si vous les appelez, par exemple. getArgument:atIndex: puis le compilateur n'aurions aucun moyen de savoir si le retour de l'argument devait être conservé.

De NSInvocation.h:

- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

Étant donné que le compilateur ne sait pas si la méthode renvoie par référence ou pas (ces deux déclarations de méthode ont les mêmes types et attributs), peut-être le migrator est (raisonnablement) de prudence et de vous dire d'éviter de nulle pointeurs à de fortes présomptions?

Par exemple:

NSDecimalNumber* val;
[anInvocation getArgument:&val atIndex:2];
anInvocation = nil;
NSLog(@"%@", val); // kaboom!


__unsafe_unretained NSDecimalNumber* tempVal;
[anInvocation getArgument:&tempVal atIndex:2];
NSDecimalNumber* val = tempVal;
anInvocation = nil;
NSLog(@"%@", val); // fine

8voto

Tammo Freese Points 5648

Un NSInvocation par défaut ne permet pas de retenir ou de copier les arguments donnés pour plus d'efficacité, de sorte que chaque objet passé en argument doit toujours vivre quand l'appel est invoquée. Cela signifie que les pointeurs passés à l' -setArgument:atIndex: sont traités de __unsafe_unretained.

Les deux lignes de MRR code que vous avez posté, s'est enfui avec ceci: testNumber1 n'a jamais été publié. Qui aurait pu entraîner une fuite de mémoire, mais aurait travaillé. Dans l'ARC même si, testNumber1 sera publié n'importe où entre la dernière utilisation et la fin du bloc dans lequel elle est définie, de sorte qu'il sera libéré. Par la migration à l'ARC, le code peut tomber en panne, de sorte que l'ARC de l'outil de migration ne vous empêche de la migration:

NSInvocation de setArgument n'est pas sûr pour être utilisé avec un objet avec la propriété autres que les __dangereuse_consignes

Tout simplement en passant le pointeur __dangereuse_consignes de ne pas résoudre le problème, vous devez vous assurer que l'argument est toujours là quand l'appel est appelée. Une façon de le faire est de téléphoner -retainArguments comme vous l'avez fait (ou encore mieux: directement après la création de l' NSInvocation). Puis l'invocation conserve tous ses arguments, et donc il garde tout ce qui est nécessaire pour être invoqué autour. C'est peut-être pas aussi efficace, mais il est certainement préférable à un crash ;)

2voto

aky Points 21

Pourquoi est-il vous empêche de le faire? Il semble juste comme de la mauvaise utilisation de __dangereuse_consignes d'objets comme arguments.

Le message d'erreur pourrait être amélioré, mais le migrateur n'est pas en disant qu' __unsafe_unretained des objets sont sûrs d'être utilisé avec d' NSInvocation (il n'y a rien de sûr avec __unsafe_unretained, c'est dans le nom). Le but de l'erreur est d'obtenir votre attention est que la transmission de force et de faiblesse des objets de l'API n'est pas sûr, votre code peut exploser au moment de l'exécution, et vous devriez vérifier le code pour s'assurer qu'il ne sera pas.

En utilisant __unsafe_unretained vous êtes essentiellement l'introduction explicite de dangereux points dans votre code où vous prenez le contrôle et la responsabilité de ce qui se passe. C'est une bonne hygiène de rendre ces dangereux de points visibles dans le code lorsque vous traitez avec des NSInvocation, au lieu d'être dans l'illusion que l'ARC sera de gérer correctement les choses avec l'API.

1voto

Joshua Weinberg Points 22701

Lance dans ma supposition terminée ici.

C'est probablement directement liée à l' retainArguments existant sur l'invocation. En général, toutes les méthodes de décrire comment ils vont gérer les arguments envoyés avec des annotations directement dans le paramètre. Qui ne peuvent pas travailler dans l' NSInvocation des cas parce que le runtime ne sait pas ce que l'invocation va faire avec le paramètre. ARC le but est de faire de son mieux pour garantir l'absence de fuites, sans ces annotations, il est au programmeur de vérifier il n'y a pas de fuite. En vous forçant à utiliser __unsafe_unretained son de vous forcer à le faire.

Je voudrais craie ce jusqu'à l'une des bizarreries avec ARC (d'autres comprennent certaines choses ne supportant pas les références faibles).

1voto

Nostradani Points 21

L'important ici est le comportement standard de NSInvocation: par défaut, les arguments ne sont pas conservés et les arguments de chaîne C ne sont pas copiés. Par conséquent, sous ARC, votre code peut se comporter comme suit:

 // Creating the testNumber
NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:@"1.0"];

// Set the number as argument
[theInvocation setArgument:&testNumber1 atIndex:2];

// At this point ARC can/will deallocate testNumber1, 
// since NSInvocation does not retain the argument
// and we don't reference testNumber1 anymore

// Calling the retainArguments method happens too late.
[theInvocation retainArguments];

// This will most likely result in a bad access since the invocation references an invalid pointer or nil
[theInvocation invoke];
 

Par conséquent, le migrateur vous dit: À ce stade, vous devez vous assurer explicitement que votre objet est conservé suffisamment longtemps. Par conséquent, créez une variable unsafe_unretained (où vous devez garder à l’esprit que ARC ne la gérera pas pour vous).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X