将c-struct放入NSArray的最佳方法是什么?

时间:2010-12-23 08:26:54

标签: iphone objective-c cocoa struct nsarray

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

值得注意的是,valueWithBytesvalueWithPointer之间有什么区别 - 由贾斯汀和鲶鱼在下面提出。

Here's a link Apple对未来读者valueWithBytes:objCType:的讨论......

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

(这提出了一个有趣的问题:是否有一个快速的c库,与STL::vector不同,但更轻,允许最小的“整理数组处理”......?)

原来的问题......

例如:

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

那么:在NSArray中存储自己的结构的正常,最佳,惯用方法是什么,以及如何用这个成语处理记忆?

请注意,我特意寻找存储结构的通常习惯用法。当然,人们可以通过创建一个新的小班来避免这个问题。但是,我想知道实际将结构放入数组中的常用习惯,谢谢。

顺便说一下BTW这可能是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];

BTW作为参考点,感谢Jarret Hardie,以下是如何在CGPoints中存储NSArray和类似内容:

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

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

12 个答案:

答案 0 :(得分:153)

NSValue不仅支持CoreGraphics结构 - 您也可以将它用于自己的结构。我建议这样做,因为对于简单的数据结构,类的重量可能比NSData轻。

只需使用如下表达式:

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

让价值回归:

Megapoint p;
[value getValue:&p];

答案 1 :(得分:7)

我建议您坚持NSValue路线,但如果您确实希望在NSArray中存储普通的struct数据类型(以及Cocoa中的其他集合对象),那么可以这样做 - 虽然间接使用Core Foundation和toll-free bridging

CFArrayRef(及其可变副本CFMutableArrayRef)在创建数组对象时为开发人员提供了更大的灵活性。请参阅指定初始化程序的第四个参数:

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

这允许您请求CFArrayRef对象使用Core Foundation的内存管理例程,完全没有,甚至您自己的内存管理例程。

强制性例子:

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

上面的例子完全忽略了内存管理方面的问题。结构myStruct在堆栈上创建,因此在函数结束时被销毁 - 数组将包含指向不再存在的对象的指针。您可以通过使用自己的内存管理例程来解决这个问题 - 因此为什么会提供给您的选项 - 但是您必须进行引用计数,分配内存,解除分配等等的艰苦工作。

我不建议使用此解决方案,但如果其他人感兴趣,请将其保留在此处。 : - )


在这里演示使用在堆上分配的结构(代替堆栈):

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

答案 2 :(得分:4)

添加c结构的类似方法是存储指针并将指针取消引用;

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 :(得分:3)

如果您在多个abis /架构之间共享此数据,最好使用穷人的objc序列化程序:

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

...特别是如果结构可以随时间变化(或通过目标平台)。它没有其他选项那么快,但它在某些条件下(你没有指定为重要或不重要)的可能性较小。

如果序列化表示没有退出进程,那么任意结构的大小/顺序/对齐不应该改变,并且有更简单和更快的选项。

在任何一种情况下,你已经添加了一个引用计数的对象(与NSData,NSValue相比)所以...在很多情况下创建一个拥有Megapoint的objc类是正确的答案。

答案 4 :(得分:3)

如果你感觉很讨厌,或者真的要创建很多类:动态构造一个objc类(参考:class_addIvar)偶尔会有用。这样,您可以从任意类型创建任意objc类。你可以逐字段指定,或者只传递结构的信息(但这实际上是复制NSData)。有时很有用,但对大多数读者来说可能更像是一个“有趣的事实”。

  

我如何在这里申请?

您可以调用class_addIvar并将Megapoint实例变量添加到新类,或者您可以在运行时合成Megapoint类的objc变体(例如,Megapoint的每个字段的实例变量)。

前者等同于编译的objc类:

@interface MONMegapoint { Megapoint megapoint; } @end

后者等同于编译的objc类:

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

添加了ivars之后,您可以添加/合成方法。

要读取接收端的存储值,请使用合成的方法object_getInstanceVariablevalueForKey:(通常会将这些标量实例变量转换为NSNumber或NSValue表示形式)。

btw:你收到的所有答案都很有用,有些是更好/更差/无效,具体取决于上下文/场景。关于记忆,速度,易于维护,易于转移或存档等方面的具体需求将决定哪种情况对于特定情况最佳......但是没有“完美”的解决方案在各方面都是理想的。没有“在NSArray中放置c-struct的最佳方法”,只是“将一个c-struct放在NSArray中以便特定场景,案例或一组要求的最佳方式” - 你必须指定的。

此外,NSArray是指针大小(或更小)类型的通用可重用数组接口,但由于多种原因,还有其他容器更适合c-structs(std :: vector是c-structs的典型选择) )。

答案 5 :(得分:0)

我建议你使用std :: vector或std :: list作为C / C ++类型,因为一开始它比NSArray快,而第二个如果没有足够的速度给你 - 你总是可以为STL容器创建自己的分配器,使它们更快。所有现代移动游戏,物理和音频引擎都使用STL容器来存储内部数据。只是因为他们真的很快。

如果它不适合你 - 那些人对NSValue有很好的答案 - 我认为这是最可接受的。

答案 6 :(得分:0)

不是试图将c struct放在NSArray中,而是可以将它们作为c数组结构放在NSData或NSMutableData中。要访问它们,您可以执行

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

或设置

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

答案 7 :(得分:0)

虽然使用NSValue可以很好地将结构存储为Obj-C对象,但是不能使用NSArchiver / NSKeyedArchiver对包含结构的NSValue进行编码。相反,您必须编码单个结构成员...

请参阅Apple's Archives and Serializations Programming Guide > Structures and Bit Fields

答案 8 :(得分:0)

对于您的结构,您可以添加attribute objc_boxable并使用@()语法将您的结构放入NSValue实例中,而无需调用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)];

答案 9 :(得分:0)

不是使用 NSArray,而是指针类型的另一个容器 NSPointerArray

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);
}

保留对象的内存是您自己的风险。

答案 10 :(得分:-2)

Obj C对象只是一个带有一些添加元素的C结构。因此,只需创建一个自定义类,您将拥有NSArray所需的C结构类型。任何没有NSObject在其C结构中包含的额外残余的C结构对于NSArray都是不可消化的。

使用NSData作为包装器可能只存储结构的副本而不是原始结构,如果这对您有所影响。

答案 11 :(得分:-3)

您可以使用C-Structures以外的NSObject类来存储信息。您可以轻松地将NSObject存储到NSArray中。