¿Cuál es la forma habitual de almacenar estructuras en C en un NSArray
? ¿Ventajas, desventajas, manejo de la memoria?
En particular, ¿cuál es la diferencia entre valueWithBytes
y valueWithPointer
- planteado por justin y bagre a continuación?
Aquí hay un enlace a la discusión de Apple valueWithBytes:objCType:
para futuros lectores ...
Para un poco de pensamiento lateral y mirando más al rendimiento, Evgen ha planteado el problema del uso STL::vector
en C ++ .
(Eso plantea un problema interesante: ¿hay una biblioteca c rápida, no muy diferente STL::vector
pero mucho más ligera, que permita el mínimo "manejo ordenado de matrices" ...?)
Entonces la pregunta original ...
Por ejemplo:
typedef struct _Megapoint {
float w,x,y,z;
} Megapoint;
Entonces: ¿cuál es la mejor manera normal, idiomática de almacenar la propia estructura así en un NSArray
, y cómo manejas la memoria en ese idioma?
Tenga en cuenta que estoy buscando específicamente el idioma habitual para almacenar estructuras. Por supuesto, uno podría evitar el problema haciendo una nueva clase pequeña. Sin embargo, quiero saber cómo se usa el idioma habitual para poner estructuras en una matriz, gracias.
Por cierto, aquí está el enfoque NSData, que tal vez sea? no es el mejor ...
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];
Por cierto, como punto de referencia y gracias a Jarret Hardie, aquí se explica cómo almacenar CGPoints
y similares en un NSArray
:
NSArray *points = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
[NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
nil];
(consulte ¿Cómo puedo agregar objetos CGPoint a un NSArray de manera fácil? )
Respuestas:
NSValue no solo es compatible con las estructuras de CoreGraphics, también puede usarlas para las suyas propias. Recomendaría hacerlo, ya que la clase probablemente sea más liviana que
NSData
para estructuras de datos simples.Simplemente use una expresión como la siguiente:
[NSValue valueWithBytes:&p objCType:@encode(Megapoint)];
Y para recuperar el valor:
Megapoint p; [value getValue:&p];
fuente
p
, no un puntero a ella. La@encode
directiva proporciona toda la información necesaria sobre el tamaño de la estructura. Cuando sueltaNSValue
(o cuando lo hace la matriz), su copia de la estructura se destruye. Si ha usadogetValue:
mientras tanto, está bien. Consulte la sección "Uso de valores" de "Temas de programación de valores y números": developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…@encode
describiría la estructura con ese puntero, pero no describiría completamente los datos apuntados, que de hecho podrían cambiar.NSValue
automáticamente la memoria de la estructura cuando se desasigne? La documentación es un poco confusa al respecto.NSValue
posee los datos que copia en sí mismo y no tengo que preocuparme por liberarlos (bajo ARC)?NSValue
en realidad, no realiza ninguna "gestión de la memoria" per se; puede pensar en ello como si tuviera una copia del valor de la estructura internamente. Si la estructura contuviera punteros anidados, por ejemplo,NSValue
no sabría liberar o copiar o hacer algo con ellos, los dejaría intactos, copiando la dirección como está.Le sugiero que se ciña a la
NSValue
ruta, pero si realmente desea almacenarstruct
tipos de datos sencillos en su NSArray (y otros objetos de colección en Cocoa), puede hacerlo, aunque de forma indirecta, utilizando Core Foundation y puentes gratuitos. .CFArrayRef
(y su contraparte mutableCFMutableArrayRef
) brindan al desarrollador más flexibilidad al crear un objeto de matriz. Vea el cuarto argumento del inicializador designado:CFArrayRef CFArrayCreate ( CFAllocatorRef allocator, const void **values, CFIndex numValues, const CFArrayCallBacks *callBacks );
Esto le permite solicitar que el
CFArrayRef
objeto use las rutinas de administración de memoria de Core Foundation, ninguna en absoluto o incluso sus propias rutinas de administración de memoria.Ejemplo obligatorio:
// 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];
El ejemplo anterior ignora por completo los problemas con respecto a la gestión de la memoria. La estructura
myStruct
se crea en la pila y, por lo tanto, se destruye cuando finaliza la función; la matriz contendrá un puntero a un objeto que ya no está allí. Puede solucionar esto utilizando sus propias rutinas de administración de memoria, de ahí la razón por la que se le brinda la opción, pero luego debe hacer el trabajo duro de contar referencias, asignar memoria, desasignarla, etc.No recomendaría esta solución, pero la mantendré aquí en caso de que sea de interés para alguien más. :-)
Aquí se demuestra el uso de su estructura asignada en el montón (en lugar de la pila):
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];
fuente
struct
, ciertamente podría asignarlo una vez y no liberarlo en el futuro. Incluí un ejemplo de esto en mi respuesta editada. Además, no fue un error tipográficomyStruct
, ya que era una estructura asignada en la pila, a diferencia de un puntero a una estructura asignada en el montón.Un método similar para agregar c struct es almacenar el puntero y quitar la referencia del puntero como tal;
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];
fuente
si te sientes nerd o si realmente tienes muchas clases para crear: ocasionalmente es útil construir dinámicamente una clase objc (ref:)
class_addIvar
. de esta manera, puede crear clases objc arbitrarias a partir de tipos arbitrarios. puede especificar campo por campo, o simplemente pasar la información de la estructura (pero eso es prácticamente replicar NSData). a veces útil, pero probablemente más un "hecho divertido" para la mayoría de los lectores.puede llamar a class_addIvar y agregar una variable de instancia Megapoint a una nueva clase, o puede sintetizar una variante objc de la clase Megapoint en tiempo de ejecución (por ejemplo, una variable de instancia para cada campo de Megapoint).
el primero es equivalente a la clase objc compilada:
@interface MONMegapoint { Megapoint megapoint; } @end
el último es equivalente a la clase objc compilada:
@interface MONMegapoint { float w,x,y,z; } @end
después de haber agregado los ivars, puede agregar / sintetizar métodos.
para leer los valores almacenados en el extremo receptor, utilice sus métodos sintetizados
object_getInstanceVariable
, ovalueForKey:
(que a menudo convertirá estas variables de instancia escalares en representaciones NSNumber o NSValue).Por cierto: todas las respuestas que ha recibido son útiles, algunas son mejores / peores / inválidas según el contexto / escenario. Las necesidades específicas en cuanto a memoria, velocidad, facilidad de mantenimiento, facilidad de transferencia o archivo, etc. determinarán cuál es la mejor para un caso dado ... pero no existe una solución "perfecta" que sea ideal en todos los aspectos. no existe una "mejor manera de poner una c-struct en un NSArray", solo una "mejor forma de poner una c-struct en un NSArray para un escenario, caso o conjunto de requisitos específicos ", que tendría para especificar.
Además, NSArray es una interfaz de matriz generalmente reutilizable para tipos de tamaño de puntero (o más pequeños), pero hay otros contenedores que son más adecuados para c-structs por muchas razones (std :: vector es una opción típica para c-structs).
fuente
std::vector
(por ejemplo) es más adecuado para contener tipos, estructuras y clases de C / C ++ que NSArray. Al usar un NSArray de tipos NSValue, NSData o NSDictionary, está perdiendo mucha seguridad de tipos mientras agrega una tonelada de asignaciones y sobrecarga de tiempo de ejecución. Si desea seguir con C, generalmente usarán malloc y / o matrices en la pila ... pero lestd::vector
ocultan la mayoría de las complicaciones.sería mejor usar el serializador objc del pobre si está compartiendo estos datos entre múltiples abis / arquitecturas:
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; /* ... */
... particularmente si la estructura puede cambiar con el tiempo (o por plataforma específica). no es tan rápido como otras opciones, pero es menos probable que se rompa en algunas condiciones (que no ha especificado como importantes o no).
si la representación serializada no sale del proceso, entonces el tamaño / orden / alineación de estructuras arbitrarias no debería cambiar, y hay opciones que son más simples y rápidas.
en cualquier caso, ya está agregando un objeto ref-counted (en comparación con NSData, NSValue) así que ... crear una clase objc que contenga Megapoint es la respuesta correcta en muchos casos.
fuente
Le sugiero que use std :: vector o std :: list para los tipos C / C ++, porque al principio es más rápido que NSArray, y en segundo lugar, si no habrá suficiente velocidad para usted, siempre puede crear la suya propia asignadores para contenedores STL y hacerlos aún más rápidos. Todos los motores de juegos, física y audio móviles modernos utilizan contenedores STL para almacenar datos internos. Solo porque realmente rápido.
Si no es para ti, hay buenas respuestas de los chicos sobre NSValue, creo que es más aceptable.
fuente
Megapoint pt[8];
; esta es una opción en c ++ y contenedores especializados de c ++ (por ejemplo,std::array
); también tenga en cuenta que el ejemplo no agrega la alineación especial (se eligieron 16 bytes porque es del tamaño de Megapoint). (cont)std::vector
agregará una pequeña cantidad de gastos generales a esto, y una asignación (si sabe el tamaño que necesitará) ... pero esto está más cerca del metal de lo que necesitan más del 99,9% de los casos. normalmente, solo usaría un vector a menos que el tamaño sea fijo o tenga un máximo razonable.En lugar de intentar poner c struct en un NSArray, puede ponerlos en un NSData o NSMutableData como una matriz de estructuras ac. Para acceder a ellos deberías hacer
const struct MyStruct * theStruct = (const struct MyStruct*)[myData bytes]; int value = theStruct[2].integerNumber;
o para configurar entonces
struct MyStruct * theStruct = (struct MyStruct*)[myData mutableBytes]; theStruct[2].integerNumber = 10;
fuente
Si bien el uso de un NSValue funciona bien para almacenar estructuras como un objeto Obj-C, no puede codificar un NSValue que contenga una estructura con NSArchiver / NSKeyedArchiver. En su lugar, debe codificar miembros de estructura individuales ...
Consulte la Guía de programación de archivos y serializaciones de Apple> Estructuras y campos de bits
fuente
Para su estructura, puede agregar un atributo
objc_boxable
y usar la@()
sintaxis para poner su estructura en la instancia de NSValue sin llamarvalueWithBytes: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)];
fuente
Un objeto Obj C es solo una estructura C con algunos elementos agregados. Así que simplemente cree una clase personalizada y tendrá el tipo de estructura C que requiere un NSArray. Cualquier estructura C que no tenga el cruft extra que un NSObject incluye dentro de su estructura C será indigerible para un NSArray.
El uso de NSData como contenedor solo podría almacenar una copia de las estructuras y no las estructuras originales, si eso hace una diferencia para usted.
fuente
Puede utilizar clases NSObject distintas de C-Structures para almacenar información. Y puede almacenar fácilmente ese NSObject en NSArray.
fuente