web-dev-qa-db-fra.com

Quelle est la meilleure façon de mettre une c-struct dans un NSArray?

Quelle est la manière habituelle de stocker des structures c dans une NSArray? Avantages, inconvénients, gestion de la mémoire?

Notamment, quelle est la différence entre valueWithBytes et valueWithPointer - élevé par justin et poisson-chat ci-dessous.

Voici un lien vers la discussion d’Apple sur le valueWithBytes:objCType: pour les futurs lecteurs ...

Pour ce qui est de la réflexion latérale et de la performance, Evgen a soulevé la question de l’utilisation de STL::vector dans C++.

(Cela soulève un problème intéressant: existe-t-il une bibliothèque rapide, semblable à STL::vector mais beaucoup plus légère, qui permet une "gestion ordonnée des tableaux" ...?)

Donc, la question initiale ...

Par exemple:

typedef struct _Megapoint {
    float   w,x,y,z;
} Megapoint;

Alors, quelle est la meilleure façon idiomatique de stocker sa propre structure ainsi dans une variable NSArray et comment gérez-vous la mémoire dans cet idiome?

Veuillez noter que je recherche spécifiquement l'idiome habituel pour stocker des structs. Bien sûr, on pourrait éviter le problème en faisant une nouvelle petite classe. Cependant, je veux savoir comment utiliser l'idiome habituel pour placer des structures dans un tableau, merci.

BTW voici l'approche NSData qui est peut-être? pas mieux ...

Megapoint p;
NSArray *a = [NSArray arrayWithObjects:
    [NSData dataWithBytes:&p length:sizeof(Megapoint)],
    [NSData dataWithBytes:&p length:sizeof(Megapoint)],
    [NSData dataWithBytes:&p length:sizeof(Megapoint)],
        nil];

BTW comme point de référence et grâce à Jarret Hardie, voici comment stocker CGPoints et similaires dans une NSArray:

NSArray *points = [NSArray arrayWithObjects:
        [NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
        [NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
        nil];

(voir Comment puis-je ajouter des objets CGPoint à un NSArray facilement? )

84
Fattie

NSValue ne supporte pas seulement les structures CoreGraphics - vous pouvez aussi l'utiliser Je le recommanderais, car la classe est probablement plus légère que NSData pour les structures de données simples.

Utilisez simplement une expression comme celle-ci:

[NSValue valueWithBytes:&p objCType:@encode(Megapoint)];

Et pour récupérer la valeur:

Megapoint p;
[value getValue:&p];
153

Je vous conseillerais de vous en tenir à la route { NSValue , mais si vous souhaitez vraiment stocker des types de données plain 'ol struct dans votre NSArray (et d'autres objets de collection dans Cocoa), vous pouvez le faire, même indirectement. Utilisation de Core Foundation et pontage sans frais .

(CFArrayRef (et son équivalent mutable, CFMutableArrayRef) offrent au développeur plus de flexibilité lors de la création d'un objet tableau. Voir le quatrième argument de l'initialisateur désigné:

CFArrayRef CFArrayCreate (
    CFAllocatorRef allocator,
    const void **values,
    CFIndex numValues,
    const CFArrayCallBacks *callBacks
);

Cela vous permet de demander que l'objet CFArrayRef utilise les routines de gestion de la mémoire de Core Foundation, pas du tout ou même vos propres routines de gestion de la mémoire.

Exemple obligatoire:

// One would pass &kCFTypeArrayCallBacks (in lieu of NULL) if using CF types.
CFMutableArrayRef arrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
NSMutableArray *array = (NSMutableArray *)arrayRef;

struct {int member;} myStruct = {.member = 42};
// Casting to "id" to avoid compiler warning
[array addObject:(id)&myStruct];

// Hurray!
struct {int member;} *mySameStruct = [array objectAtIndex:0];

L'exemple ci-dessus ignore complètement les problèmes liés à la gestion de la mémoire. La structure myStruct est créée sur la pile et est donc détruite à la fin de la fonction. Le tableau contiendra un pointeur sur un objet qui n’y est plus. Vous pouvez contourner ce problème en utilisant vos propres routines de gestion de la mémoire - d'où la raison pour laquelle l'option vous est fournie - mais vous devez ensuite effectuer le travail fastidieux du comptage des références, de l'allocation de mémoire, de sa désallocation, etc.

Je ne recommanderais pas cette solution, mais je la garderai ici au cas où elle intéresserait quelqu'un d'autre. :-)


L'utilisation de votre structure telle qu'allouée sur le tas (au lieu de la pile) est démontrée ici:

typedef struct {
    float w, x, y, z;
} Megapoint;

// One would pass &kCFTypeArrayCallBacks (in lieu of NULL) if using CF types.
CFMutableArrayRef arrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
NSMutableArray *array = (NSMutableArray *)arrayRef;

Megapoint *myPoint = malloc(sizeof(Megapoint);
myPoint->w = 42.0f;
// set ivars as desired..

// Casting to "id" to avoid compiler warning
[array addObject:(id)myPoint];

// Hurray!
Megapoint *mySamePoint = [array objectAtIndex:0];
7
Sedate Alien

Une méthode similaire pour ajouter une structure consiste à stocker le pointeur et à dé-référencer le pointeur comme tel;

typedef struct BSTNode
{
    int data;
    struct BSTNode *leftNode;
    struct BSTNode *rightNode;
}BSTNode;

BSTNode *rootNode;

//declaring a NSMutableArray
@property(nonatomic)NSMutableArray *queues;

//storing the pointer in the array
[self.queues addObject:[NSValue value:&rootNode withObjCType:@encode(BSTNode*)]];

//getting the value
BSTNode *frontNode =[[self.queues objectAtIndex:0] pointerValue];
4
Keynes

si vous vous sentez ringard ou si vous avez vraiment beaucoup de classes à créer: il est parfois utile de construire dynamiquement une classe objc (ref: class_addIvar). De cette façon, vous pouvez créer des classes objc arbitraires à partir de types arbitraires. vous pouvez spécifier champ par champ ou simplement transmettre les informations de la structure (mais cela reproduit pratiquement NSData). parfois utile, mais probablement plus «amusant» pour la plupart des lecteurs.

Comment pourrais-je appliquer ceci ici?

vous pouvez appeler class_addIvar et ajouter une variable d'instance Megapoint à une nouvelle classe ou synthétiser une variante objc de la classe Megapoint à l'exécution (par exemple, une variable d'instance pour chaque champ de Megapoint).

le premier est équivalent à la classe objc compilée:

@interface MONMegapoint { Megapoint megapoint; } @end

ce dernier est équivalent à la classe objc compilée:

@interface MONMegapoint { float w,x,y,z; } @end

après avoir ajouté les ivars, vous pouvez ajouter/synthétiser des méthodes.

pour lire les valeurs stockées du côté destinataire, utilisez vos méthodes synthétisées, object_getInstanceVariable ou valueForKey: (qui convertira souvent ces variables d’instance scalaires en représentations NSNumber ou NSValue).

au fait: toutes les réponses que vous avez reçues sont utiles, certaines sont meilleures/pires/invalides en fonction du contexte/du scénario. les besoins spécifiques en matière de mémoire, de rapidité, de facilité de maintenance, de transfert ou d'archivage, etc. détermineront ce qui convient le mieux à un cas donné ... mais il n'existe pas de solution "parfaite" idéale à tous les égards. il n'y a pas de "meilleur moyen de placer une c-struct dans un NSArray", mais simplement un "meilleur moyen de placer une c-struct dans un NSArray pour un scénario, un cas ou un ensemble d'exigences spécifique '- qui vous auriez à préciser.

en outre, NSArray est une interface de tableau généralement réutilisable pour les types de pointeur (ou de taille inférieure), mais il existe d'autres conteneurs mieux adaptés à la c-struct pour de nombreuses raisons (std :: vector étant un choix typique pour la c-struct).

3
justin

il serait préférable d'utiliser le sérialiseur objc du pauvre homme si vous partagez ces données sur plusieurs architectures/abis:

Megapoint mpt = /* ... */;
NSMutableDictionary * d = [NSMutableDictionary new];
assert(d);

/* optional, for your runtime/deserialization sanity-checks */
[d setValue:@"Megapoint" forKey:@"Type-Identifier"];

[d setValue:[NSNumber numberWithFloat:mpt.w] forKey:@"w"];
[d setValue:[NSNumber numberWithFloat:mpt.x] forKey:@"x"];
[d setValue:[NSNumber numberWithFloat:mpt.y] forKey:@"y"];
[d setValue:[NSNumber numberWithFloat:mpt.z] forKey:@"z"];

NSArray *a = [NSArray arrayWithObject:d];
[d release], d = 0;
/* ... */

... surtout si la structure peut évoluer dans le temps (ou par plate-forme ciblée). ce n'est pas aussi rapide que les autres options, mais il y a moins de risque de rupture dans certaines conditions (que vous n'avez pas spécifiées comme importantes ou non).

si la représentation sérialisée ne quitte pas le processus, la taille/ordre/alignement des structures arbitraires ne devrait pas changer, et certaines options sont plus simples et plus rapides.

dans les deux cas, vous ajoutez déjà un objet référencé (par rapport à NSData, NSValue), donc ... créer une classe objc contenant Megapoint est la bonne réponse dans de nombreux cas.

3
justin

Pour votre structure, vous pouvez ajouter un attributobjc_boxable et utiliser la syntaxe @() pour mettre votre structure en instance NSValue sans appeler valueWithBytes:objCType::

typedef struct __attribute__((objc_boxable)) _Megapoint {
    float   w,x,y,z;
} Megapoint;

NSMutableArray<NSValue*>* points = [[NSMutableArray alloc] initWithCapacity:10];
for (int i = 0; i < 10; i+= 1) {
    Megapoint mp1 = {i + 1.0, i + 2.0, i + 3.0, i + 4.0};
    [points addObject:@(mp1)];//@(mp1) creates NSValue*
}

Megapoint unarchivedPoint;
[[points lastObject] getValue:&unarchivedPoint];
//or
// [[points lastObject] getValue:&unarchivedPoint size:sizeof(Megapoint)];
0
Andrew Romanov

Bien que l'utilisation d'une NSValue fonctionne correctement pour le stockage de structures en tant qu'objet Obj-C, vous ne pouvez pas coder une NSValue contenant une structure avec NSArchiver/NSKeyedArchiver. Au lieu de cela, vous devez encoder des membres de structure individuels ...

Voir Guide de programmation Archives et sérialisation d'Apple> Structures et champs de bits

0
DarkMatter

Au lieu d'essayer de placer une structure c dans un NSArray, vous pouvez les placer dans un NSData ou NSMutableData sous la forme d'un tableau c de struct. Pour y accéder tu le ferais

const struct MyStruct    * theStruct = (const struct MyStruct*)[myData bytes];
int                      value = theStruct[2].integerNumber;

ou pour définir ensuite

struct MyStruct    * theStruct = (struct MyStruct*)[myData mutableBytes];
theStruct[2].integerNumber = 10;
0
Nathan Day

Je vous suggère d'utiliser std :: vector ou std :: list pour les types C/C++, car au début, il est juste plus rapide que NSArray, et au second, s'il n'y a pas assez de vitesse pour vous, vous pouvez toujours créer votre propre allocateurs pour les conteneurs STL et les rendre encore plus rapide. Tous les moteurs de jeu, de physique et audio mobiles modernes utilisent des conteneurs STL pour stocker des données internes. Juste parce qu'ils sont vraiment rapides.

Si ce n'est pas pour vous - il y a de bonnes réponses de la part de gars sur NSValue - je pense que c'est tout à fait acceptable.

0
Evgen Bodunov