What's the best way to put a c-struct in an NSArray?

What's the usual way to store c-structures in an NSArray? Advantages, disadvantages, memory handling?

Notably, what's the difference between valueWithBytes and valueWithPointer -- raised by justin and catfish below.

Here's a link to Apple's discussion of valueWithBytes:objCType: for future readers...

For some lateral thinking and looking more at performance, Evgen has raised the issue of using STL::vector in C++.

(That raises an interesting issue: is there a fast c library, not unlike STL::vector but much much lighter, that allows for the minimal "tidy handling of arrays" ...?)

So the original question...

For example:

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

So: what's the normal, best, idiomatic way to store one's own structure like that in an NSArray, and how do you handle memory in that idiom?

Please note that I am specifically looking for the usual idiom to store structs. Of course, one could avoid the issue by making a new little class. However I want to know how the usual idiom for actually putting structs in an array, thanks.

BTW here's the NSData approach which is perhaps? not best...

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 as a point of reference and thanks to Jarret Hardie, here's how to store CGPoints and similar in an NSArray:

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

(see How can I add CGPoint objects to an NSArray the easy way?)


Solution 1:

NSValue doesn't only support CoreGraphics structures – you can use it for your own too. I would recommend doing so, as the class is probably lighter weight than NSData for simple data structures.

Simply use an expression like the following:

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

And to get the value back out:

Megapoint p;
[value getValue:&p];

Solution 2:

I would suggest you stick to the NSValue route, but if you really do wish to store plain 'ol struct datatypes in your NSArray (and other collection objects in Cocoa), you can do so -- albeit indirectly, using Core Foundation and toll-free bridging.

CFArrayRef (and its mutable counterpart, CFMutableArrayRef) afford the developer more flexibility when creating an array object. See the fourth argument of the designated initialiser:

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

This allows you to request that the CFArrayRef object use Core Foundation's memory management routines, none at all or even your own memory management routines.

Obligatory example:

// 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];

The above example completely ignores the issues with respect to memory management. The structure myStruct is created on the stack and hence is destroyed when the function ends -- the array will contain a pointer to an object that is no longer there. You can work around this by using your own memory management routines -- hence why the option is provided to you -- but then you have to do the hard work of reference counting, allocating memory, deallocating it and so on.

I would not recommend this solution, but will keep it here in case it is of interest to anyone else. :-)


Using your structure as allocated on the heap (in lieu of the stack) is demonstrated here:

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];

Solution 3:

A similar method to add c struct is to store the pointer and to de-reference the pointer as so;

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];

Solution 4:

if you're feeling nerdy, or really have a lot of classes to create: it is occasionally useful to dynamically construct an objc class (ref: class_addIvar). this way, you can create arbitrary objc classes from arbitrary types. you can specify field by field, or just pass the info of the struct (but that's practically replicating NSData). sometimes useful, but probably more of a 'fun fact' for most readers.

How would I apply this here?

you can call class_addIvar and add a Megapoint instance variable to a new class, or you can synthesize an objc variant of the Megapoint class at runtime (e.g., an instance variable for each field of Megapoint).

the former is equivalent to the compiled objc class:

@interface MONMegapoint { Megapoint megapoint; } @end

the latter is equivalent to the compiled objc class:

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

after you've added the ivars, you can add/synthesize methods.

to read the stored values on the receiving end, use your synthesized methods, object_getInstanceVariable, or valueForKey:(which will often convert these scalar instance variables into NSNumber or NSValue representations).

btw: all the answers you have received are useful, some are better/worse/invalid depending on the context/scenario. specific needs regarding memory, speed, ease to maintain, ease to transfer or archive, etc. will determine which is best for a given case... but there is no 'perfect' solution which is ideal in every regard. there is no 'best way to put a c-struct in an NSArray', just a 'best way to put a c-struct in an NSArray for a specific scenario, case, or set of requirements' -- which you'd have to specify.

furthermore, NSArray is a generally reusable array interface for pointer sized (or smaller) types, but there are other containers which are better suited for c-structs for many reasons (std::vector being an typical choice for c-structs).