将 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 结构的常用方法是什么?优点、缺点、内存处理?
值得注意的是,valueWithBytes
和 valueWithPointer
之间有什么区别——由 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_getInstanceVariable
或 valueForKey:
(通常会将这些标量实例变量转换为 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 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章