Qual è il modo migliore per inserire una struttura c in un NSArray?


89

Qual è il modo usuale per memorizzare le strutture C in un NSArray? Vantaggi, svantaggi, gestione della memoria?

In particolare, qual è la differenza tra valueWithBytese valueWithPointer - allevato da justin e pesce gatto di seguito.

Ecco un collegamento alla discussione di Apple valueWithBytes:objCType:per i futuri lettori ...

Per un po 'di pensiero laterale e per guardare più alle prestazioni, Evgen ha sollevato il problema dell'uso STL::vectorin C ++ .

(Ciò solleva una questione interessante: esiste una libreria c veloce, non diversa STL::vectorma molto più leggera, che consente la minima "gestione ordinata degli array" ...?)

Quindi la domanda originale ...

Per esempio:

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

Quindi: qual è il modo normale, migliore, idiomatico per memorizzare la propria struttura in quel modo in un NSArray, e come gestisci la memoria in quel linguaggio?

Si prega di notare che sto cercando specificamente il solito idioma per memorizzare gli struct. Naturalmente, si potrebbe evitare il problema creando una nuova piccola classe. Tuttavia voglio sapere come è il solito idioma per mettere effettivamente le strutture in un array, grazie.

A proposito, ecco l'approccio NSData che è forse? non migliore ...

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

A proposito come punto di riferimento e grazie a Jarret Hardie, ecco come archiviare CGPointse simili in un NSArray:

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

(vedi Come posso aggiungere oggetti CGPoint a un NSArray in modo semplice? )


il codice per convertirlo in NSData dovrebbe andare bene .. e senza perdite di memoria .... tuttavia, si potrebbe anche usare un array C ++ standard di strutture Megapoint p [3];
Swapnil Luktuke

Non puoi aggiungere una taglia finché la domanda non ha due giorni.
Matthew Frederick

1
valueWithCGPoint non è disponibile per OSX però. Fa parte di UIKit
lppier

@Ippier valueWithPoint è disponibile su OS X
Schpaencoder

Risposte:


160

NSValue non supporta solo le strutture CoreGraphics, puoi usarlo anche per le tue. Consiglierei di farlo, poiché la classe è probabilmente più leggera rispetto NSDataa strutture di dati semplici.

Usa semplicemente un'espressione come la seguente:

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

E per recuperare il valore:

Megapoint p;
[value getValue:&p];

4
@Joe Blow @Catfish_Man In realtà copia la struttura p, non un puntatore ad essa. La @encodedirettiva fornisce tutte le informazioni necessarie su quanto è grande la struttura. Quando rilasci il NSValue(o quando l'array lo fa), la sua copia della struttura viene distrutta. Se l'hai usata getValue:nel frattempo, stai bene. Consulta la sezione "Utilizzo dei valori" di "Argomenti sulla programmazione di numeri e valori": developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Justin Spahr-Summers

1
@ Joe Blow Per lo più corretto, tranne che non sarebbe in grado di cambiare in fase di esecuzione. Stai specificando un tipo C, che deve sempre essere completamente noto. Se potesse diventare "più grande" facendo riferimento a più dati, allora probabilmente lo implementeresti con un puntatore e @encodedescriveresti la struttura con quel puntatore, ma non descriveresti completamente i dati puntati, che potrebbero effettivamente cambiare.
Justin Spahr-Summers

1
Libererà NSValueautomaticamente la memoria della struttura quando viene deallocata? La documentazione è un po 'poco chiara su questo.
devios1

1
Quindi, per essere completamente chiari, NSValuepossiede i dati che copia in se stesso e non devo preoccuparmi di liberarlo (sotto ARC)?
devios1

1
@devios Correct. NSValuein realtà non fa alcuna "gestione della memoria", di per sé: puoi pensare che abbia solo una copia del valore della struttura internamente. Se la struttura contenesse puntatori annidati, per esempio, NSValuenon saprebbe come liberarli, copiarli o fare qualcosa con quelli, li lascerebbe intatti, copiando l'indirizzo così com'è.
Justin Spahr-Summers

7

Suggerirei di attenersi al NSValuepercorso, ma se si desidera davvero memorizzare semplici structtipi di dati nel proprio NSArray (e altri oggetti di raccolta in Cocoa), è possibile farlo, anche se indirettamente, utilizzando Core Foundation e bridging senza pedaggio .

CFArrayRef(e la sua controparte mutevole CFMutableArrayRef) offrono allo sviluppatore maggiore flessibilità durante la creazione di un oggetto array. Vedere il quarto argomento dell'inizializzatore designato:

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

Ciò consente di richiedere che l' CFArrayRefoggetto utilizzi le routine di gestione della memoria di Core Foundation, nessuna o addirittura le proprie routine di gestione della memoria.

Esempio obbligatorio:

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

L'esempio sopra ignora completamente i problemi relativi alla gestione della memoria. La struttura myStructviene creata sullo stack e quindi viene distrutta quando la funzione termina: l'array conterrà un puntatore a un oggetto che non è più lì. Puoi aggirare questo problema usando le tue routine di gestione della memoria - ecco perché l'opzione ti viene fornita - ma poi devi fare il duro lavoro del conteggio dei riferimenti, dell'allocazione della memoria, della deallocazione e così via.

Non consiglierei questa soluzione, ma la terrò qui nel caso interessasse qualcun altro. :-)


L'utilizzo della struttura allocata nell'heap (al posto dello stack) è illustrato qui:

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

Gli array mutabili (almeno in questo caso) vengono creati in uno stato vuoto e quindi non hanno bisogno di un puntatore ai valori da memorizzare all'interno. Questo è diverso dall'array immutabile in cui i contenuti sono "congelati" alla creazione e quindi i valori devono essere passati alla routine di inizializzazione.
Sedate Alien

@ Joe Blow: Questo è un ottimo punto che fai riguardo alla gestione della memoria. Hai ragione a essere confuso: il codice di esempio che ho postato sopra causerebbe misteriosi arresti anomali, a seconda di quando lo stack della funzione viene sovrascritto. Ho iniziato a elaborare su come utilizzare la mia soluzione, ma mi sono reso conto che stavo reimplementando il conteggio dei riferimenti di Objective-C. Mi scuso per il codice compatto: non è questione di attitudine ma di pigrizia. Non ha senso scrivere codice che gli altri non possono leggere. :)
Sedate Alien

Se tu fossi felice di "trapelare" (in mancanza di una parola migliore) struct, potresti certamente assegnarlo una volta e non liberarlo in futuro. Ho incluso un esempio di questo nella mia risposta modificata. Inoltre, non era un errore di battitura myStruct, poiché era una struttura allocata nello stack, distinta da un puntatore a una struttura allocata nell'heap.
Sedate Alien

4

Un metodo simile per aggiungere c struct consiste nel memorizzare il puntatore e nel de-referenziare il puntatore in questo modo;

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

se ti senti nerd o hai davvero molte classi da creare: a volte è utile costruire dinamicamente una classe objc (ref :) class_addIvar. in questo modo, puoi creare classi objc arbitrarie da tipi arbitrari. puoi specificare campo per campo, o semplicemente passare le informazioni della struttura (ma questo sta praticamente replicando NSData). a volte utile, ma probabilmente più di un "fatto divertente" per la maggior parte dei lettori.

Come lo applicherei qui?

puoi chiamare class_addIvar e aggiungere una variabile di istanza Megapoint a una nuova classe, oppure puoi sintetizzare una variante objc della classe Megapoint in fase di runtime (ad esempio, una variabile di istanza per ogni campo di Megapoint).

il primo è equivalente alla classe objc compilata:

@interface MONMegapoint { Megapoint megapoint; } @end

quest'ultima è equivalente alla classe objc compilata:

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

dopo aver aggiunto gli ivars, puoi aggiungere / sintetizzare metodi.

per leggere i valori memorizzati sull'estremità ricevente, utilizzare i metodi sintetizzati object_getInstanceVariable, o valueForKey:(che spesso convertirà queste variabili di istanza scalari in rappresentazioni NSNumber o NSValue).

btw: tutte le risposte che hai ricevuto sono utili, alcune sono migliori / peggiori / non valide a seconda del contesto / scenario. esigenze specifiche di memoria, velocità, facilità di manutenzione, facilità di trasferimento o archiviazione, ecc. determineranno quale sia la migliore per un determinato caso ... ma non esiste una soluzione "perfetta" che sia ideale sotto ogni aspetto. non esiste un "modo migliore per mettere una struttura c in un NSArray", ma solo un "modo migliore per mettere una struttura c in un NSArray per uno scenario, un caso o un insieme di requisiti specifici ", che avresti specificare.

inoltre, NSArray è un'interfaccia di array generalmente riutilizzabile per tipi di dimensioni puntatore (o più piccoli), ma ci sono altri contenitori che sono più adatti per le strutture c per molte ragioni (std :: vector è una scelta tipica per le strutture c).


Anche il background delle persone entra in gioco ... il modo in cui devi usare quella struttura spesso elimina alcune possibilità. 4 float è abbastanza infallibile, ma i layout della struttura variano troppo in base all'architettura / compilatore per utilizzare una rappresentazione di memoria contigua (ad esempio, NSData) e aspettarsi che funzioni. il serializzatore objc del povero uomo probabilmente ha il tempo di esecuzione più lento, ma è il più compatibile se è necessario salvare / aprire / trasmettere il Megapoint su qualsiasi dispositivo OS X o iOS. Il modo più comune, nella mia esperienza, è semplicemente mettere la struttura in una classe objc. se stai attraversando tutto questo solo per (segue)
justin

(segue) Se stai affrontando tutta questa seccatura solo per evitare di imparare un nuovo tipo di raccolta, allora dovresti imparare che il nuovo tipo di raccolta =) std::vector(per esempio) è più adatto a contenere tipi, strutture e classi C / C ++ rispetto NSArray. Usando un NSArray di tipi NSValue, NSData o NSDictionary, stai perdendo molta sicurezza dei tipi aggiungendo un sacco di allocazioni e overhead di runtime. Se vuoi restare con C, generalmente useranno malloc e / o array nello stack ... ma ti std::vectornascondono la maggior parte delle complicazioni.
justin

infatti, se vuoi manipolare / iterare gli array come hai detto, stl (parte delle librerie standard c ++) è ottimo per questo. hai più tipi tra cui scegliere (ad esempio, se inserire / rimuovere è più importante dei tempi di accesso in lettura) e tonnellate di modi esistenti per manipolare i contenitori. inoltre, non è memoria nuda in c ++, i contenitori e le funzioni dei modelli sono compatibili con i tipi e controllati durante la compilazione, molto più sicuri che estrarre una stringa di byte arbitraria dalle rappresentazioni NSData / NSValue. hanno anche il controllo dei limiti e la gestione prevalentemente automatica della memoria. (cont)
justin

(segue) se ti aspetti che avrai un sacco di lavoro di basso livello come questo, allora dovresti impararlo ora, ma ci vorrà del tempo per imparare. avvolgendo tutto questo in rappresentazioni objc, stai perdendo molte prestazioni e sicurezza dei tipi, mentre ti costringi a scrivere molto più codice standard per accedere e interpretare i contenitori e i loro valori (Se 'Quindi, per essere molto specifici ...' è esattamente quello che vuoi fare).
justin

è solo un altro strumento a tua disposizione. possono esserci complicazioni nell'integrazione di objc con c ++, c ++ con objc, c con c ++ o una qualsiasi delle molte altre combinazioni. l'aggiunta di funzionalità linguistiche e l'utilizzo di più lingue ha comunque un piccolo costo. va in tutti i modi. per esempio, i tempi di compilazione aumentano quando si compila come objc ++. inoltre, queste fonti non vengono riutilizzate facilmente in altri progetti. certo, puoi reimplementare le funzionalità del linguaggio ... ma spesso non è la soluzione migliore. l'integrazione di c ++ in un progetto objc va bene, è "disordinato" quanto l'utilizzo di sorgenti objc ec nello stesso progetto. (segue
justin

3

sarebbe meglio usare il serializzatore objc dei poveri se condividi questi dati su più abis / architetture:

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

... soprattutto se la struttura può cambiare nel tempo (o dalla piattaforma mirata). non è veloce come le altre opzioni, ma è meno probabile che si rompa in alcune condizioni (che non hai specificato come importanti o meno).

se la rappresentazione serializzata non esce dal processo, allora dimensione / ordine / allineamento di strutture arbitrarie non dovrebbero cambiare e ci sono opzioni che sono più semplici e veloci.

in entrambi i casi, stai già aggiungendo un oggetto ref-counted (rispetto a NSData, NSValue) quindi ... creare una classe objc che contiene Megapoint è la risposta giusta in molti casi.


@ Joe Blow qualcosa che esegue la serializzazione. per riferimento: en.wikipedia.org/wiki/Serialization , parashift.com/c++-faq-lite/serialization.html , nonché "Archives and Serializations Programming Guide" di Apple.
justin

supponendo che il file xml rappresenti correttamente qualcosa, allora sì, è una forma comune di serializzazione leggibile dall'uomo.
justin

0

Ti suggerisco di usare std :: vector o std :: list per i tipi C / C ++, perché all'inizio è solo più veloce di NSArray, e in secondo luogo se non ci sarà abbastanza velocità per te, puoi sempre crearne uno tuo allocatori per contenitori STL e renderli ancora più veloci. Tutti i moderni motori di gioco, fisica e audio per dispositivi mobili utilizzano contenitori STL per memorizzare i dati interni. Solo perché sono davvero veloci.

Se non è per te - ci sono buone risposte da parte di ragazzi su NSValue - penso che sia più accettabile.


STL è una libreria parzialmente inclusa nella libreria standard C ++. en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Evgen Bodunov

Questa è un'affermazione interessante. Hai un collegamento a un articolo sul vantaggio in termini di velocità dei contenitori STL rispetto alle classi di contenitori Cocoa?
Sedate Alien

ecco una lettura interessante su NSCFArray vs std :: vector: ridiculousfish.com/blog/archives/2005/12/23/array nell'esempio nel tuo post, la perdita più grande è (tipicamente) la creazione di una rappresentazione dell'oggetto objc per elemento (ad es. , NSValue, NSData o Objc contenente Megapoint richiede un'allocazione e un inserimento nel sistema conteggio ref). potresti effettivamente evitarlo utilizzando l'approccio di Sedate Alien alla memorizzazione di un Megapoint in uno speciale CFArray che utilizza un backing store separato di Megapoint allocati in modo contiguo (sebbene nessuno dei due esempi illustri tale approccio). (segue)
Justin

ma poi l'utilizzo di NSCFArray rispetto al vettore (o altro tipo stl) comporterà un sovraccarico aggiuntivo per l'invio dinamico, chiamate di funzioni aggiuntive che non sono inline, una tonnellata di sicurezza dei tipi e molte possibilità per l'ottimizzatore di dare il via ... che l'articolo si concentra solo su inserire, leggere, camminare, eliminare. è probabile che non sarai più veloce di un c-array allineato a 16 byte Megapoint pt[8];- questa è un'opzione in c ++ e contenitori c ++ specializzati (ad esempio std::array) - nota anche che l'esempio non aggiunge l'allineamento speciale (sono stati scelti 16 byte perché è la dimensione di Megapoint). (segue)
justin

std::vectoraggiungerà una piccola quantità di overhead a questo e un'allocazione (se conosci la dimensione di cui avrai bisogno) ... ma questo è più vicino al metallo di quanto non sia necessario oltre il 99,9% dei casi. in genere, useresti solo un vettore a meno che la dimensione non sia fissa o abbia un massimo ragionevole.
justin

0

Invece di provare a mettere c struct in un NSArray, puoi metterli in un NSData o NSMutableData come array ac di struct. Per accedervi fareste

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

o per impostare quindi

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


0

Per la tua struttura puoi aggiungere un attributo objc_boxable e utilizzare la @()sintassi per inserire la tua struttura nell'istanza NSValue senza chiamare 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

Un oggetto Obj C è solo una struttura C con alcuni elementi aggiunti. Quindi crea una classe personalizzata e avrai il tipo di struttura C richiesta da un NSArray. Qualsiasi struttura C che non ha la cruft extra che un NSObject include nella sua struttura C sarà indigeribile per un NSArray.

L'utilizzo di NSData come wrapper potrebbe memorizzare solo una copia delle strutture e non le strutture originali, se questo fa la differenza per te.


-3

È possibile utilizzare classi NSObject diverse dalle C-Structures per memorizzare le informazioni. E puoi facilmente memorizzare quell'NSObject in NSArray.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.