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


89

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

值得注意的是,有什么之间的区别valueWithBytesvalueWithPointer -由Justin及以下鲶鱼提出。

这是苹果valueWithBytes:objCType:为将来的读者讨论的链接...

对于某些横向思考并更多地关注性能,Evgen提出了STL::vectorC ++中使用的问题。

(这引起了一个有趣的问题:是否有一个快速的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];

BTW作为参考,并感谢Jarret Hardie,这是在其中存储CGPoints和类似内容的方法NSArray

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

(请参阅如何以简单的方式将CGPoint对象添加到NSArray?


您的代码将其转换为NSData应该很好..并且没有内存泄漏....但是,您不妨使用标准的C ++结构数组Megapoint p [3];
Swapnil Luktuke 2010年

在问题出现两天之前,您无法添加奖金。
马修·弗雷德里克

1
但是valueWithCGPoint不适用于OSX。它是UIKit的一部分
lppier

@Ippier valueWithPoint在OS X上可用
Schpaencoder

Answers:


159

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

只需使用如下表达式:

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

并获得价值回馈:

Megapoint p;
[value getValue:&p];

4
@Joe Blow @Catfish_Man它实际上是复制结构p,而不是指向它的指针。该@encode指令提供有关结构大小的所有必要信息。释放时NSValue(或数组释放时),其结构副本将被销毁。如果您getValue:同时使用过,就可以了。见“宗数和金额编程主题”中的“使用值”节:developer.apple.com/library/ios/documentation/Cocoa/Conceptual/...
贾斯汀斯帕尔-萨默斯

1
@Joe吹大多是正确的,但它能够在运行时改变。您要指定C类型,该类型必须始终是完全已知的。如果它可以通过引用更多数据而变得“更大”,那么您可能会使用一个指针来实现该功能,并且@encode它将使用该指针来描述结构,但不会完全描述指向的数据,而这种数据实际上可能会发生变化。
贾斯汀·斯帕夏

1
NSValue释放结构时会自动释放该结构的内存吗?文档对此尚不清楚。
devios1

1
因此,为了完全清楚NSValue起见,它拥有复制到其自身中的数据,而我不必担心释放它(在ARC中)?
devios1 2015年

1
@devios正确。NSValue实际上,它实际上并没有执行任何“内存管理”-您可以将其视为仅在内部复制结构值。例如,如果结构包含嵌套指针,NSValue则不知道要释放或复制嵌套指针或对其进行任何操作,它将使它们保持不变,直接复制地址。
贾斯汀·斯帕夏

7

我建议您坚持走这NSValue条路线,但是如果您确实希望将纯'olstruct数据类型存储在NSArray(以及Cocoa中的其他集合对象)中,则可以这样做-尽管是间接使用Core Foundation和免费电话桥接

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

可变数组(至少在这种情况下)在空状态下创建,因此不需要指向要存储在其中的值的指针。这与不可变数组不同,在不可变数组中,内容在创建时被“冻结”,因此必须将值传递给初始化例程。
塞达(Sedate Alien)2010年

@Joe Blow:关于内存管理,这是一个很好的观点。您应该感到困惑:我在上面发布的代码示例将导致神秘的崩溃,具体取决于函数堆栈何时被覆盖。我开始详细说明如何使用我的解决方案,但是意识到我正在重新实现Objective-C自己的引用计数。我对紧凑代码表示歉意-这不是能力的问题,而是懒惰。编写其他人无法阅读的代码毫无意义。:)
Sedate Alien 2010年

如果您乐于“泄漏”(因为需要一个更好的词)struct,则可以肯定地分配它一次,并且将来不释放它。我在编辑后的答案中包含了一个示例。另外,它不是的错字myStruct,因为它是在堆栈上分配的结构,与指向堆上分配的结构的指针不同。
塞塔特外星人

4

添加c struct的一种类似方法是存储指针并如此取消对指针的引用。

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

如果您感到讨厌,或者确实要创建很多类:动态构造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_getInstanceVariable,或valueForKey:(通常会将这些标量实例变量转换为NSNumber或NSValue表示形式)。

顺便说一句:您收到的所有答案都是有用的,根据上下文/情况,有些答案更好/更差/无效。有关内存,速度,易于维护,易于传输或存档等方面的特定需求将确定哪种方法最适合给定情况……但是没有“完美”的解决方案在各个方面都是理想的。没有“在NSArray中放置c结构的最佳方法”,只有“针对特定场景,案例或一组要求的将c结构放置在NSArray中的最佳方法”-您将拥有指定。

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


人们的背景也会发挥作用...您需要如何使用该结构通常会消除一些可能性。4个浮点数非常简单,但是结构布局因体系结构/编译器而异,以至于无法使用连续的内存表示形式(例如NSData)并期望其起作用。穷人的objc序列化程序执行时间可能最慢,但是如果您需要在任何OS X或iOS设备上保存/打开/传输Megapoint,它都是最兼容的。以我的经验,最常见的方法是将结构简单地放在objc类中。如果您正在经历所有这些只是(续)
justin 2010年

(续)如果您为了避免学习新的收集类型而经历了所有麻烦,那么您应该学习新的收集类型=)std::vector(例如)比持有C / C ++类型,结构和类更适合NSArray。通过使用NSValue,NSData或NSDictionary类型的NSArray,您将失去很多类型安全性,同时增加了大量的分配和运行时开销。如果您想坚持使用C,那么它们通常会在堆栈上使用malloc和/或数组...但是std::vector对您隐藏了大多数麻烦。
贾斯丁2010年

实际上,如果您要像您提到的那样要进行数组操作/迭代-stl(c ++标准库的一部分)非常有用。您有更多类型可以选择(例如,如果插入/删除比读取访问时间更重要),还有许多现有的操作容器的方法。同样-它不是c ++中的裸内存-容器和模板函数都可以识别类型,并在编译时进行检查-比从NSData / NSValue表示形式中拉出任意字节字符串安全得多。他们也有边界检查和自动内存管理功能。(续)
贾斯汀2010年

(续)如果您希望自己会做很多这样的低级工作,那么您应该现在就学习它-但是这将需要一些时间来学习。通过以objc表示形式包装所有内容,您将失去很多性能和类型安全性,同时使自己编写更多的样板代码来访问和解释容器及其值(如果“因此,非常具体……”是正是您想要的)。
贾斯丁2010年

它只是您可以使用的另一种工具。将objc与c ++集成,将c ++与objc集成,将c与c ++集成或其他几种组合中的任何一种都可能会比较复杂。在任何情况下,添加语言功能并使用多种语言的确花费很小。一切都有。例如,当编译为objc ++时,构建时间会增加。同样,这些资源在其他项目中也不太容易重用。当然,您可以重新实现语言功能...但这通常不是最好的解决方案。将C ++集成到objc项目中就可以了,就像在同一项目中使用objc和c源代码一样“混乱”。(续
justin 2010年

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类是正确的答案。


@Joe吹一些执行序列化的东西。供参考:en.wikipedia.org/wiki/Serializationparashift.com/c++-faq-lite/serialization.html,以及苹果的“归档和序列化编程指南”。
贾斯汀2010年

假设xml文件正确地代表了某种东西,那么是的-这是人类可读序列化的一种常见形式。
贾斯汀2010年

0

我建议您对C / C ++类型使用std :: vector或std :: list,因为起初它的速度比NSArray快,而第二步如果速度不够,那么您始终可以创建自己的速度STL容器的分配器,并使它们更快。所有现代的移动游戏,物理和音频引擎都使用STL容器存储内部数据。只是因为它们真的很快。

如果不适合您-有人对NSValue给出了很好的答案-我认为这是最可接受的。



这是一个有趣的主张。您是否链接到有关STL容器相对于Cocoa容器类的速度优势的文章?
塞达(Sedate Alien)2010年

这是您帖子中示例中关于NSCFArray vs std :: vector:ridiculousfish.com/blog/archives/2005/12/23/array的有趣读物,最大的损失是(通常)为每个元素创建objc对象表示(例如,包含Megapoint的NSValue,NSData或Objc类型需要分配并插入到引用计数系统中)。您实际上可以通过使用Sedate Alien的方法将Megapoint存储在特殊的CFArray中来避免这种情况,该CFArray使用连续分配的Megapoint的单独后备存储(尽管两个示例均未说明该方法)。(续)
贾斯汀2010年

但是然后使用NSCFArray与向量(或其他stl类型)进行比较将导致动态分配的额外开销,未内联的其他函数调用,大量类型安全性以及优化程序启动的很多机会...本文仅关注插入,阅读,遍历,删除。Megapoint pt[8];很有可能,您将不会获得比16字节对齐的c数组更快的速度-这是c ++和专用c ++容器中的一个选项(例如std::array),还请注意该示例未添加特殊对齐方式(选择了16字节因为它是Megapoint的大小)。(续)
贾斯汀2010年

std::vector会为此增加少量的开销,并且会分配一个(如果您知道需要的大小)...但是这比金属更接近金属,超过了99.9%的情况。通常,除非大小固定或具有合理的最大值,否则您只会使用向量。
贾斯汀2010年

0

您可以将它们作为ac数组放置在NSData或NSMutableData中,而不是尝试将c放置在NSArray中。要访问它们,您会做

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;


0

对于您的结构,您可以添加属性 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)];

-2

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

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


-3

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.