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

Quelle est la façon habituelle de stocker les structures en c dans un NSArray ? Avantages, inconvénients, gestion de la mémoire?

notamment, Quelle est la différence entre valueWithBytes et valueWithPointer -- élevé par justin et catfish ci-dessous.

Voici un lien de la part d'Apple de discussion de valueWithBytes:objCType: pour les futurs lecteurs...

pour une certaine pensée latérale et en regardant plus à la performance, Evgen a a soulevé la question de l'utilisation de STL::vector dans C++ .

(qui soulève une question intéressante: y a-t-il une bibliothèque C Rapide, pas à la différence de STL::vector mais beaucoup plus léger, qui permet le minimum" maniement ordonné des tableaux"...?)

donc la question originale...

par exemple:

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

donc: quelle est la façon normale, la meilleure, idiomatique de stocker sa propre structure dans un NSArray , et comment gérer la mémoire dans cet idiome?

s'il vous Plaît noter que je recherche précisément l'habitude idiome pour stocker des structures. Bien sûr, on pourrait éviter le problème en faisant une nouvelle petite classe. Cependant je veux savoir comment l'idiome habituel pour mettre réellement 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 similaire dans un 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 the easy way? )

82
demandé sur Fattie 2010-12-23 11:26:54

10 réponses

NSValue ne supporte pas seulement les structures Corégraphiques – vous pouvez l'utiliser pour les vôtres aussi. Je recommande de le faire, car la classe est probablement plus légère que NSData pour les structures de données simples.

utilisez simplement une expression comme la suivante:

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

Et pour obtenir la valeur de retour:

Megapoint p;
[value getValue:&p];
152
répondu Justin Spahr-Summers 2010-12-23 10:40:34

je vous suggérerais de vous en tenir à la route NSValue , mais si vous voulez vraiment stocker des types de données simples" ol struct dans votre NSArray (et d'autres objets de collection dans le cacao), vous pouvez le faire-bien qu'indirectement, en utilisant la fondation de base et pont sans frais .

CFArrayRef (et son équivalent mutable, CFMutableArrayRef ) offrent au développeur plus de flexibilité lors de la création un objet de tableau. Voir le quatrième argument de l'initialiseur 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 la Fondation Core, aucune ou même votre propre 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 relatifs à la gestion de la mémoire. Structure myStruct est créé sur la pile, et donc est détruite lorsque la fonction se termine -- le tableau contient un pointeur vers un objet qui n'est plus là. Vous pouvez contourner cela en utilisant vos propres routines de gestion de la mémoire -- d'où la raison pour laquelle l'option vous est fournie -- mais alors vous devez faire le travail difficile de compter les références, allouer la mémoire, la délocaliser et ainsi de suite.

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


L'utilisation de votre structure telle qu'attribué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
répondu Sedate Alien 2014-05-28 13:48:13

si vous vous sentez Nerd, ou avez vraiment beaucoup de classes à créer: il est parfois utile de construire dynamiquement une classe objc (réf: class_addIvar ). de cette façon, vous pouvez créer des classes objc arbitraires à partir de types arbitraires. vous pouvez spécifier un champ par champ, ou simplement passer l'information de la structure (mais c'est pratiquement répliquer NSData). parfois utile, mais probablement plus "amusant" pour la plupart des lecteurs.

Comment appliquer ceci ici?

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

la première est équivalente à 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é le ivars, vous pouvez ajouter/synthétiser des méthodes.

pour lire les valeurs stockées sur l'extrémité de réception, utilisez vos méthodes synthétisées, object_getInstanceVariable , ou valueForKey: (qui convertira souvent ces variables d'instance scalaire en représentations de NSNumber ou NSValue).

btw: toutes les réponses que vous avez reçues sont utiles, certaines sont meilleures/pires/invalides selon le contexte/scénario. besoins spécifiques concernant la mémoire, la rapidité, la facilité à maintenir, la facilité de transférer ou archives, etc. détermineront ce qui est le mieux pour un cas donné... mais il n'y a pas de solution "parfaite" qui soit idéale à tous égards. il n'y a pas de "meilleure façon de mettre un c-struct dans un NSArray", juste une "meilleure façon de mettre un c-struct dans un NSArray pour un scénario, un cas, ou un ensemble d'exigences spécifiques " que vous auriez à spécifier.

en outre, NSArray est une interface réseau généralement réutilisable pour les pointeurs de taille (ou plus petit) types, mais il ya d'autres conteneurs qui sont mieux adaptés pour C-structs pour de nombreuses raisons (std::vecteur étant un choix typique pour C-structs).

3
répondu justin 2010-12-29 10:21:50

il serait préférable d'utiliser le serializer objc du pauvre homme si vous partagez ces données entre 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;
/* ... */

...en particulier si la structure peut changer avec le temps (ou par plate-forme ciblée). ce n'est pas aussi rapide que les autres options, mais il est moins probable de se briser dans certaines conditions (que vous n'avez pas précisé comme important ou non).

si la représentation sérialisée ne sort pas du processus, alors taille/afin/de l'alignement de l'arbitraire des structures ne doit pas changer, et il y a des options qui sont plus simples et plus rapides.

dans les deux cas, vous ajoutez déjà un objet ref-counted (comparé à NSData, NSValue) donc... créer une classe objc qui contient Megapoint est la bonne réponse dans de nombreux cas.

3
répondu justin 2010-12-29 23:37:50

Une méthode similaire pour ajouter struct c est pour stocker le pointeur et de référence de l'aiguille;

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];
3
répondu Keynes 2015-04-21 15:42:20

je vous suggère d'utiliser std::vector ou std::list pour les types C/C++, car au début C'est juste plus rapide que NSArray, et au second si il n'y aura pas assez de vitesse pour vous - vous êtes toujours capable de créer vos propres allocateurs pour les conteneurs STL et de les rendre encore plus rapides. Tous les moteurs de jeux mobiles modernes, physiques et Audio utilise des conteneurs STL pour stocker des données internes. Juste parce qu'ils ont vraiment rapide.

si ce n'est pas pour vous - Il ya de bonnes réponses de gars sur NSValue - je pense que c'est plus acceptable.

0
répondu Evgen Bodunov 2010-12-29 16:05:26

au lieu d'essayer de mettre c struct dans un NSArray, vous pouvez les mettre dans un NSData ou NSMutableData comme un tableau de C structs. Pour y accéder vous feriez le do

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

ou alors

struct MyStruct    * theStruct = (struct MyStruct*)[myData mutableBytes];
theStruct[2].integerNumber = 10;
0
répondu Nathan Day 2013-05-17 07:53:38

en utilisant une valeur NS fonctionne très bien pour stocker des structures comme un objet Obj-C, vous ne pouvez pas encoder une valeur NS contenant une structure avec NSArchiver/NSKeyedArchiver. Au lieu de cela, vous devez encoder les membres de struct individuels...

voir Apple Archives and Serializations Programming Guide > Structures and Bit Fields

0
répondu DarkMatter 2013-05-25 15:44:21

un objet Obj c est juste une structure C avec quelques éléments ajoutés. Donc créez juste une classe personnalisée et vous aurez le type de structure c qu'un NSArray nécessite. Toute structure C qui n'a pas la croûte supplémentaire qu'un NSObject inclut dans sa structure c sera indigeste à un NSArray.

utiliser NSData comme wrapper pourrait ne stocker qu'une copie des structures et non les structures originales, si cela fait une différence pour vous.

-2
répondu hotpaw2 2010-12-23 10:45:12

vous pouvez utiliser des classes NSObject autres que les C-Structures pour stocker des informations. Et vous pouvez facilement stocker ce NSObject dans NSArray.

-3
répondu Satya 2016-06-21 21:13:56