2 votes

Objet NSFastEnumeration casting en ARC

Je suis en train d'implémenter la méthode countByEnumeratingWithState:objects:count: du protocole NSFastEnumeration sur une classe personnalisée.

Jusqu'à présent, j'ai réussi à parcourir mes objets correctement, mais les objets retournés ne sont pas des objets Objective-C mais plutôt les équivalents du framework Core Foundation.

Voici la partie du code qui définit state->itemsPtr:

MyCustomCollection.m

- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState *)state
                                   objects: (id __unsafe_unretained *)buffer
                                    count: (NSUInteger)bufferSize {

    // ... ignorer les détails ...

    NSLog(@"Objet à l'intérieur de la méthode : %@", someObject);
    state->itemsPtr = (__unsafe_unretained id *)(__bridge void *)someObject;      

    // ... ignorer les détails ...
}

Ensuite, j'appelle la boucle 'for..in' ailleurs comme ceci

SomeOtherClass.m

MyCustomCollection *myCustomCollection = [MyCustomCollection new];
[myCustomCollection addObject:@"foo"];
for (id object in myCustomCollection) {
    NSLog(@"Objet dans la boucle : %@", object);
}

La sortie dans la console est la suivante :

Objet à l'intérieur de la méthode : foo
Objet dans la boucle : __NSCFConstantString

Comme vous pouvez le voir, à l'intérieur de la méthode du protocole NSFastEnumeration, l'objet s'affiche correctement, mais dès qu'il est converti en id __unsafe_unretained *, je perds la classe Objective-C correspondante d'origine.

Pour être honnête, je ne suis pas tout à fait sûr de comment fonctionne la conversion (__unsafe_unretained id *)(__bridge void *) dans ce cas. Le (__unsafe_unretained id *) semble convertir pour correspondre au bon type nécessaire pour itemsPtr. Le (__bridge void *) semble convertir en un pointeur de type void avec __bridge utilisé pour faire le pont entre le monde obj-c et le monde CF. Selon les docs llvm, pour __bridge :

 

Il n'y a pas de transfert de propriété et ARC n'insère aucune opération de conservation

Est-ce correct ?

D'après ce que je comprends, __NSCFConstantString est simplement l'équivalent du framework Core Foundation de NSString. Je comprends également qu'avec ARC, vous devez faire le pont entre les objets Objective-C et les équivalents du framework CoreFoundation car ARC ne sait pas comment gérer la mémoire de ces derniers.

Comment puis-je faire en sorte que cela fonctionne pour que les objets dans ma boucle 'for..in' soient du type d'origine ?

Remarquez également que dans ce cas, j'ajoute des NSStrings à ma collection mais en théorie, cela devrait prendre en charge n'importe quel objet.

MISE À JOUR

La réponse de Rob est sur la bonne voie, mais pour tester cette théorie, j'ai changé la boucle for comme ceci :

for (id object in myCustomCollection) {
    NSString *stringObject = (NSString *)object;
    NSLog(@"Chaîne %@ longueur : %d", stringObject, [stringObject length]);
}

En théorie, cela devrait fonctionner puisque les objets sont équivalents mais cela plante avec cette erreur :

+[__NSCFConstantString length]: unrecognized selector sent to class

Il semble presque que les objets retournés dans la boucle for sont des classes et non des instances. Quelque chose d'autre pourrait être erroné ici... Des idées à ce sujet ?

MISE À JOUR 2 : SOLUTION

C'est aussi simple que cela : (merci à CodaFi)

state->itemsPtr = &someObject;

3voto

CodaFi Points 29073

Vous lancez de manière incorrecte someObject. Ce que vous vouliez dire est :

~~state->itemsPtr = (__unsafe_unretained id *)(__bridge void *)&someObject;
~~

(Débarrassons-nous aussi de ces affreux casts)

state->itemsPtr = &someObject;

Sans l'opérateur adresse, votre variable est poussée dans le premier pointeur, qui est déréférencé dans la boucle. Lorsqu'il est déréférencé (essentiellement, *id), vous obtenez le pointeur de classe isa de l'objc_object sous-jacent plutôt qu'un objet. C'est pourquoi le débogueur imprime la valeur de la chaîne à l'intérieur de l'appel de l'énumérateur, et la classe de l'objet à l'intérieur de la boucle, et pourquoi l'envoi d'un message au pointeur résultant lance une exception.

1voto

rob mayoff Points 124153

Votre code est correct tel qu'il est. Votre sortie de débogage révèle un détail d'implémentation.

NSString est toll-free-bridged avec CFString. Cela signifie que vous pouvez traiter n'importe quelle NSString comme un CFString, ou vice versa, simplement en convertissant le pointeur vers l'autre type.

En fait, sous le capot, les chaînes de caractères constantes au moment de la compilation sont des instances du type __NSCFConstantString, c'est ce que vous voyez.

Si vous mettez @"bonjour" dans votre code source, le compilateur le traite comme un NSString * et le compile en une instance de __NSCFConstantString.

Si vous mettez CFSTR("bonjour") dans votre code source, le compilateur le traite comme un CFStringRef et le compile en une instance de __NSCFConstantString.

À l'exécution, il n'y a aucune différence entre ces objets en mémoire, même si vous avez utilisé une syntaxe différente pour les créer dans votre code source.

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