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

Posted

技术标签:

【中文标题】将 c-struct 放入 NSArray 的最佳方法是啥?【英文标题】:What's the best way to put a c-struct in an NSArray?将 c-struct 放入 NSArray 的最佳方法是什么? 【发布时间】:2011-05-29 20:31:02 【问题描述】:

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

值得注意的是,valueWithBytesvalueWithPointer 之间有什么区别——由 justin 和 catfish 下面提出。

Here's a link Apple 为未来的读者讨论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];

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

【问题讨论】:

您将其转换为 NSData 的代码应该没问题......并且没有内存泄漏......但是,不妨使用标准的 C++ 结构体 Megapoint p[3] 数组;跨度> 在问题出现两天之前,您无法添加赏金。 valueWithCGPoint 不适用于 OSX。它是 UIKit 的一部分 @Ippier valueWithPoint 在 OS X 上可用 【参考方案1】:

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

只需使用如下表达式:

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

然后将值取回:

Megapoint p;
[value getValue:&p];

【讨论】:

@Joe Blow @Catfish_Man 它实际上复制了结构p,而不是指向它的指针。 @encode 指令提供了有关结构大小的所有必要信息。当您释放NSValue(或当数组释放时),它的结构副本被破坏。如果您在此期间使用过getValue:,那就没问题了。请参阅“数字和值编程主题”的“使用值”部分:developer.apple.com/library/ios/documentation/Cocoa/Conceptual/… @Joe Blow 基本正确,只是它不会能够在运行时更改。您正在指定一个 C 类型,它必须始终是完全已知的。如果它可以通过引用更多数据而变得“更大”,那么您可能会使用指针来实现它,而@encode 将使用该指针描述结构,但 not 完全描述了指向数据,这确实可以改变。 NSValue 会在结构体被释放时自动释放它的内存吗?文档对此有点不清楚。 所以为了完全清楚,NSValue 拥有它复制到自己的数据,我不必担心释放它(在 ARC 下)? @devios 正确。 NSValue 本身并没有真正做任何“内存管理”——你可以认为它只是在内部拥有结构值的副本。例如,如果结构包含嵌套指针,NSValue 将不知道释放或复制这些指针或对它们做任何事情——它会保持它们不变,照原样复制地址。【参考方案2】:

我建议您坚持使用NSValue 路线,但如果您确实希望在您的 NSArray(以及 Cocoa 中的其他集合对象)中存储普通的 'ol struct 数据类型,您可以这样做——尽管是间接的,使用 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];

【讨论】:

可变数组(至少在这种情况下)是在空状态下创建的,因此不需要指向要存储的值的指针。这与不可变数组不同,其中内容在创建时“冻结”,因此必须将值传递给初始化例程。 @Joe Blow:您在内存管理方面提出的观点非常好。你是对的:我上面发布的代码示例会导致神秘的崩溃,这取决于函数的堆栈何时被覆盖。我开始详细说明如何使用我的解决方案,但意识到我正在重新实现 Objective-C 自己的引用计数。我为紧凑的代码道歉——这不是能力问题,而是懒惰问题。编写其他人无法阅读的代码毫无意义。 :) 如果您乐于“泄露”(因为需要更好的词)struct,您当然可以分配它一次,以后不再释放它。我在我编辑的答案中包含了一个例子。此外,这不是 myStruct 的拼写错误,因为它是在堆栈上分配的结构,与指向在堆上分配的结构的指针不同。【参考方案3】:

一个类似的添加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];

【讨论】:

【参考方案4】:

如果你觉得书呆子,或者真的有很多类要创建:动态构造一个 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 表示形式)。

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

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

【讨论】:

人们的背景也会发挥作用......你需要如何使用该结构通常会消除一些可能性。 4 个浮点数非常简单,但结构布局因架构/编译器而异,无法使用连续的内存表示(例如 NSData)并期望它能够工作。穷人的 objc 序列化程序可能执行时间最慢,但如果您需要在任何 OS X 或 iOS 设备上保存/打开/传输 Megapoint,它是最兼容的。根据我的经验,最常见的方法是将结构简单地放在 objc 类中。如果您正在经历所有这些只是为了(续) (cont) 如果您经历所有这些麻烦只是为了避免学习新的集合类型——那么您应该学习新的集合类型 =)std::vector(例如)更适合比 NSArray 持有 C/C++ 类型、结构和类。通过使用 NSValue、NSData 或 NSDictionary 类型的 NSArray,在增加大量分配和运行时开销的同时,您会失去很多类型安全性。如果您想坚持使用 C,那么他们通常会在堆栈上使用 malloc 和/或数组...但是 std::vector 隐藏了您的大部分复杂情况。 事实上,如果你想像你提到的那样进行数组操作/迭代 - stl(c++ 标准库的一部分)非常适合。您有更多类型可供选择(例如,如果插入/删除比读取访问时间更重要),以及大量现有的容器操作方法。此外 - 它不是 C++ 中的裸内存 - 容器和模板函数是类型感知的,并在编译时检查 - 比从 NSData/NSValue 表示中提取任意字节字符串要安全得多。他们也有边界检查和内存的自动管理。 (续) (cont) 如果您预计自己会有很多这样的低级工作,那么您现在就应该学习它 - 但学习需要时间。通过将这一切都包装在 objc 表示中,您将失去很多性能和类型安全性,同时让自己编写更多样板代码来访问和解释容器及其值(如果“因此,非常具体......”是正是你想做的)。 这只是您可以使用的另一个工具。将 objc 与 c++、c++ 与 objc、c 与 c++ 或其他几种组合中的任何一种集成可能会出现复杂情况。无论如何,添加语言功能和使用多种语言确实需要付出很小的代价。一切顺利。例如,编译为 objc++ 时构建时间会增加。同样,这些资源也不能轻易地在其他项目中重用。当然,您可以重新实现语言功能......但这通常不是最好的解决方案。将 c++ 集成到 objc 项目中很好,就像在同一个项目中使用 objc 和 c 源代码一样“混乱”。(继续【参考方案5】:

如果您要跨多个 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 Blow 执行序列化的东西。供参考:en.wikipedia.org/wiki/Serialization、parashift.com/c++-faq-lite/serialization.html,以及 Apple 的《归档和序列化编程指南》。 假设 xml 文件正确地表示了某些东西,那么是的 - 这是人类可读序列化的一种常见形式。【参考方案6】:

我建议您将 std::vector 或 std::list 用于 C/C++ 类型,因为首先它比 NSArray 快,其次如果速度不够,您总是可以的为 STL 容器创建您自己的分配器并使它们更快。所有现代移动游戏、物理和音频引擎都使用 STL 容器来存储内部数据。只是因为他们真的很快。

如果它不适合你——关于 NSValue 的人给出了很好的答案——我认为这是最可以接受的。

【讨论】:

STL 是部分包含在 C++ 标准库中的库。 en.wikipedia.org/wiki/Standard_Template_Librarycplusplus.com/reference/stl/vector 这是一个有趣的说法。您是否有关于 STL 容器与 Cocoa 容器类的速度优势的文章的链接? 这里有一篇关于 NSCFArray 与 std::vector 的有趣读物:ridiculousfish.com/blog/archives/2005/12/23/array 在您帖子的示例中,最大的损失是(通常)为每个元素创建一个 objc 对象表示(例如,NSValue、NSData ,或包含 Megapoint 的 Objc 类型需要分配并插入到引用计数系统中)。您实际上可以通过使用 Sedate Alien 的方法将 Megapoint 存储在一个特殊的 CFArray 中来避免这种情况,该 CFArray 使用连续分配的 Megapoints 的单独后备存储(尽管这两个示例都没有说明这种方法)。 (续) 但随后使用 NSCFArray 与向量(或其他 stl 类型)将导致动态调度的额外开销、未内联的额外函数调用、大量类型安全以及许多机会优化器启动...那篇文章只关注插入、读取、遍历、删除。很有可能,你不会比 16 字节对齐的 c 数组 Megapoint pt[8]; 更快 - 这是 c++ 中的一个选项,以及专门的 c++ 容器(例如,std::array) - 另请注意,该示例没有添加特殊的对齐(选择 16 字节,因为它是 Megapoint 的大小)。 (续) std::vector 将为此增加少量开销,并且一次分配(如果您知道您需要的大小)...但这比 99.9% 以上更接近金属的情况下需要。通常,除非大小是固定的或具有合理的最大值,否则您只会使用向量。【参考方案7】:

您可以将它们作为 c 结构数组放入 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;

【讨论】:

【参考方案8】:

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

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

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

除了使用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);


保留对象的内存需要您自担风险。

【讨论】:

这获得了十年前的问题徽章!【参考方案11】:

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

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

【讨论】:

【参考方案12】:

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

【讨论】:

以上是关于将 c-struct 放入 NSArray 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

将 C++ 类优化为与 C-struct 一样快

NSArray 无法识别我放入其中的内容并给我错误

NSArray 排序和隔离

将两个数组中的所有字符串放入另一个数组[重复]

将 PHP JSON 转换为 NSArray

如何将 NSArray 中的对象添加到 NSMutableArray 的末尾?