在 NSArray 中放置 c-struct 的最佳方法是什么?

NSArray中存储 c 结构的常用方法是什么? 优点、缺点、内存处理?

值得注意的是,valueWithBytesvalueWithPointer之间的区别是什么。

这里有一个链接 ,为未来的读者提供苹果公司关于 valueWithBytes:objCType:的讨论..。

对于一些水平思考和更多地关注性能,Evgen 提出了在 C + + 中使用 STL::vector的问题。

(这引发了一个有趣的问题: 是否有一个快速的 c 库,与 STL::vector没有什么不同,但要轻得多,它允许最小限度地“整齐地处理数组”... ... ?)

所以最初的问题是..。

例如:

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

那么,在 NSArray中存储自己的结构的最常见、最好、最惯用的方法是什么呢? 在这种惯用方法中,您如何处理内存?

请注意,我正在特别寻找存储结构的常用习惯用法。当然,可以通过创建一个新的小类来避免这个问题。然而,我想知道在数组中实际放置结构的常用习惯用法是怎样的,谢谢。

顺便说一下,这里的 NSData 方法也许不是最好的..。

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

顺便说一句,感谢 Jarret Hardie,以下是如何在 NSArray中存储 CGPoints和类似的内容:

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

(见 我怎样才能简单地将 CGPoint 对象添加到 NSArray 中?)

28299 次浏览

An Obj C object is just a C struct with some added elements. So just create a custom class and you will have the type of C struct that an NSArray requires. Any C struct that does not have the extra cruft that an NSObject includes within its C struct will be indigestible to an NSArray.

Using NSData as a wrapper might only be storing a copy of the structs and not the original structs, if that makes a difference to you.

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

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

it would be best to use the poor-man's objc serializer if you're sharing this data across multiple abis/architectures:

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;
/* ... */

...particularly if the structure can change over time (or by targeted platform). it's not as fast as other options, but it's less likely to break in some conditions (which you haven't specified as important or not).

if the serialized representation does not exit the process, then size/order/alignment of arbitrary structs should not change, and there are options which are simpler and faster.

in either event, you're already adding a ref-counted object (compared to NSData, NSValue) so... creating an objc class which holds Megapoint is the right answer in many cases.

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).

I suggest you to use std::vector or std::list for C/C++ types, because at first it's just faster than NSArray, and at second if there will be not enough speed for you - you're always can create your own allocators for STL containers and make them even more fast. All modern mobile Game, Physics and Audio engines uses STL containers to store internal data. Just because they really fast.

If it's not for you - there is good answers from guys about NSValue - i think it's most acceptable.

You can use NSObject classes other than the C-Structures to store information. And you can easily store that NSObject in to NSArray.

Instead of trying to put c struct in a NSArray you can put them in a NSData or NSMutableData as a c array of structs. To access them you would the do

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

or to set then

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

While using an NSValue works fine for storing structs as an Obj-C object, you cannot encode an NSValue containing a struct with NSArchiver/NSKeyedArchiver. Instead, you have to encode individual struct members...

See Apple's Archives and Serializations Programming Guide > Structures and Bit Fields

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

For your structure you can add an attribute objc_boxable and use @() syntax to put of your structure into NSValue instance without calling 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)];

Instead of using NSArray, there is another container NSPointerArray for the pointer type:

Megapoint point = ...;
NSPointerArray *arr = [NSPointerArray weakObjectsPointerArray];


[arr addPointer:&point];


for (NSUInteger i = 0; i < arr.count; i++) {
Megapoint *ppoint = [arr pointerAtIndex:i];
NSLog(@"%p", ppoint);
}


It's your own risk to retain the object's memory.