Was ist der beste Weg, um eine c-Struktur in ein NSArray einzufügen?

87

Was ist der übliche Weg, um C-Strukturen in einem zu speichern NSArray? Vor- und Nachteile, Speicherhandhabung?

Insbesondere, was ist der Unterschied zwischen valueWithBytesund valueWithPointer - von Justin und Wels unten aufgezogen.

Hier ist ein Link zu Apples Diskussion valueWithBytes:objCType:für zukünftige Leser ...

Für ein wenig Querdenken und mehr Blick auf die Leistung hat Evgen das Problem der Verwendung STL::vectorin C ++ angesprochen .

(Das wirft ein interessantes Problem auf: Gibt es eine schnelle c-Bibliothek, die nicht anders, STL::vectoraber viel leichter ist und die minimale "ordentliche Handhabung von Arrays" ermöglicht ...?)

Also die ursprüngliche Frage ...

Beispielsweise:

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

Also: Was ist die normale, beste und idiomatische Art, die eigene Struktur so in einem zu speichern NSArray, und wie geht man mit dem Gedächtnis in dieser Sprache um?

Bitte beachten Sie, dass ich speziell nach der üblichen Sprache zum Speichern von Strukturen suche. Natürlich könnte man das Problem vermeiden, indem man eine neue kleine Klasse bildet. Ich möchte jedoch wissen, wie die übliche Redewendung zum tatsächlichen Einfügen von Strukturen in ein Array funktioniert, danke.

Übrigens ist hier der NSData-Ansatz, der vielleicht ist? nicht am besten ...

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

Übrigens als Referenz und dank Jarret Hardie, hier ist, wie man CGPointsund ähnliches in einem NSArray:

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

(Siehe Wie kann ich CGPoint-Objekte auf einfache Weise zu einem NSArray hinzufügen? )

Fattie
quelle
Ihr Code für die Konvertierung in NSData sollte in Ordnung sein. Und ohne Speicherlecks. Sie können jedoch auch ein Standard-C ++ - Array von Strukturen verwenden. Megapoint p [3];
Swapnil Luktuke
Sie können kein Kopfgeld hinzufügen, bis die Frage zwei Tage alt ist.
Matthew Frederick
1
valueWithCGPoint ist für OSX jedoch nicht verfügbar. Es ist Teil von UIKit
lppier
@Ippier valueWithPoint ist verfügbar unter OS X
Schpaencoder

Antworten:

157

NSValue unterstützt nicht nur CoreGraphics-Strukturen, sondern kann auch für eigene Zwecke verwendet werden. Ich würde dies empfehlen, da die Klasse wahrscheinlich leichter ist als NSDatafür einfache Datenstrukturen.

Verwenden Sie einfach einen Ausdruck wie den folgenden:

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

Und um den Wert wieder herauszuholen:

Megapoint p;
[value getValue:&p];
Justin Spahr-Summers
quelle
4
@Joe Blow @Catfish_Man Es kopiert tatsächlich die Struktur p, keinen Zeiger darauf. Die @encodeRichtlinie enthält alle erforderlichen Informationen darüber, wie groß die Struktur ist. Wenn Sie das freigeben NSValue(oder wenn das Array dies tut), wird seine Kopie der Struktur zerstört. Wenn Sie getValue:in der Zwischenzeit verwendet haben, geht es Ihnen gut. Siehe den Abschnitt "Verwenden von Werten" unter "Themen zur Programmierung von Zahlen und Werten": developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Justin Spahr-Summers
1
@ Joe Blow Meistens richtig, außer dass es sich zur Laufzeit nicht ändern kann. Sie geben einen C-Typ an, der immer vollständig bekannt sein muss. Wenn es durch Verweisen auf mehr Daten "größer" werden könnte, würden Sie dies wahrscheinlich mit einem Zeiger implementieren und @encodedie Struktur mit diesem Zeiger beschreiben, aber die Daten, auf die verwiesen wird, nicht vollständig beschreiben, was sich tatsächlich ändern könnte.
Justin Spahr-Summers
1
Gibt NSValueder Speicher der Struktur automatisch frei, wenn die Zuordnung aufgehoben wird? Die Dokumentation ist dazu etwas unklar.
devios1
1
NSValueUm ganz klar zu sein, besitzt der Eigentümer die Daten, die er in sich selbst kopiert, und ich muss mir keine Sorgen um die Freigabe machen (unter ARC)?
Devios1
1
@devios Richtig. NSValueführt per se keine „Speicherverwaltung“ durch - Sie können sich vorstellen, dass nur eine Kopie des Strukturwerts intern vorhanden ist. Wenn die Struktur beispielsweise verschachtelte Zeiger enthalten NSValuewürde , würde sie diese nicht freigeben, kopieren oder damit etwas anfangen können - sie würden unberührt bleiben und die Adresse unverändert kopieren.
Justin Spahr-Summers
7

Ich würde vorschlagen, dass Sie sich an die NSValueRoute halten, aber wenn Sie wirklich einfache structDatentypen in Ihrem NSArray (und anderen Sammlungsobjekten in Cocoa) speichern möchten , können Sie dies tun - wenn auch indirekt mithilfe von Core Foundation und gebührenfreiem Bridging .

CFArrayRef(und sein veränderliches Gegenstück CFMutableArrayRef) bieten dem Entwickler mehr Flexibilität beim Erstellen eines Array-Objekts. Siehe das vierte Argument des bezeichneten Initialisierers:

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

Auf diese Weise können Sie anfordern, dass das CFArrayRefObjekt die Speicherverwaltungsroutinen von Core Foundation verwendet, überhaupt keine oder sogar Ihre eigenen Speicherverwaltungsroutinen.

Obligatorisches Beispiel:

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

Das obige Beispiel ignoriert die Probleme in Bezug auf die Speicherverwaltung vollständig. Die Struktur myStructwird auf dem Stapel erstellt und daher beim Beenden der Funktion zerstört. Das Array enthält einen Zeiger auf ein Objekt, das nicht mehr vorhanden ist. Sie können dies umgehen, indem Sie Ihre eigenen Speicherverwaltungsroutinen verwenden - daher wird Ihnen die Option zur Verfügung gestellt -, aber dann müssen Sie die harte Arbeit des Referenzzählens, Zuweisen von Speicher, Freigeben von Speicher usw. erledigen.

Ich würde diese Lösung nicht empfehlen, werde sie aber hier aufbewahren, falls sie für andere von Interesse ist. :-)


Die Verwendung Ihrer auf dem Heap zugewiesenen Struktur (anstelle des Stapels) wird hier demonstriert:

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];
Beruhige Alien
quelle
Veränderbare Arrays werden (zumindest in diesem Fall) in einem leeren Zustand erstellt und benötigen daher keinen Zeiger auf die darin zu speichernden Werte. Dies unterscheidet sich von dem unveränderlichen Array, bei dem der Inhalt bei der Erstellung "eingefroren" wird und daher Werte an die Initialisierungsroutine übergeben werden müssen.
Sedate Alien
@ Joe Blow: Das ist ein ausgezeichneter Punkt, den Sie in Bezug auf die Speicherverwaltung ansprechen. Sie sind zu Recht verwirrt: Das oben veröffentlichte Codebeispiel würde mysteriöse Abstürze verursachen, je nachdem, wann der Stapel der Funktion überschrieben wird. Ich begann zu überlegen, wie meine Lösung verwendet werden könnte, stellte jedoch fest, dass ich die Referenzzählung von Objective-C neu implementierte. Ich entschuldige mich für den kompakten Code - es geht nicht um Eignung, sondern um Faulheit. Es macht keinen Sinn, Code zu schreiben, den andere nicht lesen können. :)
Sedate Alien
Wenn Sie glücklich wären, das zu "lecken" (aus Mangel an einem besseren Wort) struct, könnten Sie es sicherlich einmal zuweisen und es in Zukunft nicht mehr freigeben . Ich habe ein Beispiel dafür in meine bearbeitete Antwort aufgenommen. Es war auch kein Tippfehler für myStruct, da es sich um eine auf dem Stapel zugewiesene Struktur handelte, die sich von einem Zeiger auf eine auf dem Heap zugewiesene Struktur unterschied.
Sedate Alien
4

Eine ähnliche Methode zum Hinzufügen von c struct besteht darin, den Zeiger zu speichern und den Zeiger so zu de-referenzieren.

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];
Keynes
quelle
3

Wenn Sie sich nerdig fühlen oder wirklich viele Klassen erstellen müssen: Es ist gelegentlich nützlich, eine Objektklasse dynamisch zu erstellen (ref :) class_addIvar. Auf diese Weise können Sie beliebige Objektklassen aus beliebigen Typen erstellen. Sie können Feld für Feld angeben oder einfach die Informationen der Struktur übergeben (dies repliziert jedoch praktisch NSData). manchmal nützlich, aber für die meisten Leser wahrscheinlich eher eine "lustige Tatsache".

Wie würde ich das hier anwenden?

Sie können class_addIvar aufrufen und einer neuen Klasse eine Megapoint-Instanzvariable hinzufügen, oder Sie können zur Laufzeit eine objc-Variante der Megapoint-Klasse synthetisieren (z. B. eine Instanzvariable für jedes Feld von Megapoint).

Ersteres entspricht der kompilierten objc-Klasse:

@interface MONMegapoint { Megapoint megapoint; } @end

Letzteres entspricht der kompilierten objc-Klasse:

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

Nachdem Sie die Ivars hinzugefügt haben, können Sie Methoden hinzufügen / synthetisieren.

Verwenden Sie zum Lesen der gespeicherten Werte auf der Empfangsseite Ihre synthetisierten Methoden object_getInstanceVariableoder valueForKey:(die diese skalaren Instanzvariablen häufig in NSNumber- oder NSValue-Darstellungen konvertieren).

Übrigens: Alle Antworten, die Sie erhalten haben, sind nützlich, einige sind je nach Kontext / Szenario besser / schlechter / ungültig. Spezifische Anforderungen in Bezug auf Speicher, Geschwindigkeit, einfache Wartung, einfache Übertragung oder Archivierung usw. bestimmen, welche für einen bestimmten Fall am besten geeignet sind. Es gibt jedoch keine „perfekte“ Lösung, die in jeder Hinsicht ideal ist. Es gibt keine "beste Möglichkeit, eine C-Struktur in ein NSArray einzufügen", sondern nur eine "beste Möglichkeit, eine C-Struktur in ein NSArray für ein bestimmtes Szenario, einen bestimmten Fall oder eine Reihe von Anforderungen einzufügen " - die Sie hätten konkretisieren.

Darüber hinaus ist NSArray eine allgemein wiederverwendbare Array-Schnittstelle für Zeigergrößen (oder kleinere), es gibt jedoch auch andere Container, die aus vielen Gründen besser für c-Strukturen geeignet sind (std :: vector ist eine typische Wahl für c-Strukturen).

Justin
quelle
Die Hintergründe der Menschen spielen ebenfalls eine Rolle. Wenn Sie diese Struktur verwenden müssen, werden häufig einige Möglichkeiten ausgeschlossen. 4 Floats sind ziemlich narrensicher, aber die Strukturlayouts variieren je nach Architektur / Compiler zu stark, um eine zusammenhängende Speicherdarstellung (z. B. NSData) zu verwenden und zu erwarten, dass sie funktioniert. Der objc-Serializer des armen Mannes hat wahrscheinlich die langsamste Ausführungszeit, ist jedoch am kompatibelsten, wenn Sie den Megapoint auf einem OS X- oder iOS-Gerät speichern / öffnen / übertragen müssen. Nach meiner Erfahrung ist es am gebräuchlichsten, die Struktur einfach in eine objc-Klasse zu setzen. Wenn Sie all dies nur bis (Fortsetzung)
durchgehen
(Forts.) Wenn Sie all diesen Aufwand nur durchlaufen, um das Erlernen eines neuen Sammlungstyps zu vermeiden, sollten Sie lernen, dass der neue Sammlungstyp =) std::vector(zum Beispiel) besser für das Halten von C / C ++ - Typen, -Strukturen und -Klassen geeignet ist als NSArray. Durch die Verwendung eines NSArray der Typen NSValue, NSData oder NSDictionary verlieren Sie viel Typensicherheit, während Sie eine Menge Zuordnungen und Laufzeit-Overhead hinzufügen. Wenn Sie bei C bleiben möchten, verwenden sie im Allgemeinen Malloc und / oder Arrays auf dem Stapel ... std::vectorverbergen jedoch die meisten Komplikationen vor Ihnen.
Justin
Wenn Sie wie bereits erwähnt eine Manipulation / Iteration von Arrays wünschen, ist stl (Teil der C ++ - Standardbibliotheken) genau das Richtige für Sie. Sie haben mehr Typen zur Auswahl (z. B. wenn das Einfügen / Entfernen wichtiger ist als die Lesezugriffszeiten) und unzählige Möglichkeiten, die Container zu manipulieren. Außerdem - es ist kein bloßer Speicher in C ++ - sind die Container und Vorlagenfunktionen typabhängig und werden beim Kompilieren überprüft - viel sicherer als das Abrufen einer beliebigen Byte-Zeichenfolge aus NSData / NSValue-Darstellungen. Sie haben auch Grenzen zu überprüfen und meist automatische Verwaltung des Speichers. (Fortsetzung)
Justin
(Fortsetzung) Wenn Sie erwarten, dass Sie so viel Arbeit auf niedrigem Niveau haben, sollten Sie es jetzt lernen - aber es wird einige Zeit dauern, bis Sie es gelernt haben. Wenn Sie dies alles in objc-Darstellungen zusammenfassen, verlieren Sie viel Leistung und Typensicherheit, während Sie viel mehr Code für das Boilerplate schreiben, um auf die Container und ihre Werte zuzugreifen und sie zu interpretieren (wenn 'Also, um sehr spezifisch zu sein ...') genau das, was Sie tun möchten).
Justin
Es ist nur ein weiteres Werkzeug, das Ihnen zur Verfügung steht. Es kann Komplikationen bei der Integration von objc in c ++, c ++ in objc, c in c ++ oder einer von mehreren anderen Kombinationen geben. Das Hinzufügen von Sprachfunktionen und die Verwendung mehrerer Sprachen ist in jedem Fall mit geringen Kosten verbunden. es geht alle Wege. Beispielsweise werden die Erstellungszeiten beim Kompilieren als objc ++ erhöht. Außerdem werden diese Quellen in anderen Projekten nicht so einfach wiederverwendet. Natürlich können Sie Sprachfunktionen neu implementieren ... aber das ist oft nicht die beste Lösung. Die Integration von c ++ in ein objc-Projekt ist in Ordnung. Es ist ungefähr so ​​"chaotisch" wie die Verwendung von objc- und c-Quellen im selben Projekt. (Fortsetzung
30.
3

Es ist am besten, den Objekt-Serializer des armen Mannes zu verwenden, wenn Sie diese Daten über mehrere Abis / Architekturen hinweg teilen:

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

... insbesondere wenn sich die Struktur im Laufe der Zeit ändern kann (oder durch eine gezielte Plattform). Es ist nicht so schnell wie andere Optionen, aber es ist weniger wahrscheinlich, dass es unter bestimmten Bedingungen (die Sie nicht als wichtig angegeben haben oder nicht) nicht funktioniert.

Wenn die serialisierte Darstellung den Prozess nicht beendet, sollte sich Größe / Reihenfolge / Ausrichtung beliebiger Strukturen nicht ändern, und es gibt Optionen, die einfacher und schneller sind.

In beiden Fällen fügen Sie bereits ein neu gezähltes Objekt hinzu (im Vergleich zu NSData, NSValue). In vielen Fällen ist es die richtige Antwort, eine objc-Klasse zu erstellen, die Megapoint enthält.

Justin
quelle
@ Joe Blow etwas, das Serialisierung durchführt. Als Referenz: en.wikipedia.org/wiki/Serialization , parashift.com/c++-faq-lite/serialization.html sowie Apples "Programmierhandbuch für Archive und Serialisierungen".
Justin
Angenommen, die XML-Datei repräsentiert etwas richtig, dann ja - es ist eine übliche Form einer für Menschen lesbaren Serialisierung.
Justin
0

Ich empfehle Ihnen, std :: vector oder std :: list für C / C ++ - Typen zu verwenden, da es zunächst nur schneller als NSArray ist und zweitens, wenn es nicht genug Geschwindigkeit für Sie gibt, können Sie immer Ihre eigene erstellen Allokatoren für STL-Container und machen sie noch schneller. Alle modernen mobilen Spiel-, Physik- und Audio-Engines verwenden STL-Container zum Speichern interner Daten. Nur weil sie wirklich schnell sind.

Wenn es nicht für dich ist - es gibt gute Antworten von Leuten über NSValue - denke ich, dass es am akzeptabelsten ist.

Evgen Bodunov
quelle
STL ist eine Bibliothek, die teilweise in der C ++ Standard Library enthalten ist. en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Evgen Bodunov
Das ist eine interessante Behauptung. Haben Sie einen Link zu einem Artikel über den Geschwindigkeitsvorteil der STL-Container gegenüber den Cocoa-Containerklassen?
Beruhige Alien
Hier ist eine interessante Lektüre zu NSCFArray vs std :: vector: lächerlichfish.com/ blog/archives/2005/12/23/array In dem Beispiel in Ihrem Beitrag besteht der größte Verlust darin, (normalerweise) eine Objektobjektdarstellung pro Element zu erstellen (z Der Typ "NSValue", "NSData" oder "Objc", der Megapoint enthält, erfordert eine Zuordnung und Einfügung in das neu gezählte System. Sie könnten dies tatsächlich vermeiden, indem Sie den Ansatz von Sedate Alien zum Speichern eines Megapoints in einem speziellen CFArray verwenden, der einen separaten Hintergrundspeicher mit zusammenhängend zugewiesenen Megapoints verwendet (obwohl keines der beiden Beispiele diesen Ansatz veranschaulicht). (Fortsetzung)
Justin
Die Verwendung von NSCFArray im Vergleich zum Vektor (oder einem anderen STL-Typ) verursacht jedoch zusätzlichen Overhead für den dynamischen Versand, zusätzliche Funktionsaufrufe, die nicht inline sind, eine Menge Typensicherheit und viele Chancen für den Optimierer, sich darauf einzulassen Artikel konzentriert sich nur auf Einfügen, Lesen, Gehen, Löschen. Es besteht die Möglichkeit, dass Sie nicht schneller als ein 16-Byte-ausgerichtetes C-Array werden Megapoint pt[8];- dies ist eine Option in C ++ und speziellen C ++ - Containern (z. B. std::array) -. Beachten Sie auch, dass im Beispiel die spezielle Ausrichtung nicht hinzugefügt wird (16 Byte wurden ausgewählt weil es die Größe von Megapoint ist). (Fortsetzung)
Justin
std::vectorDies führt zu einem geringen Overhead und einer Zuordnung (wenn Sie die benötigte Größe kennen). Dies ist jedoch näher am Metall, als mehr als 99,9% der Fälle benötigen. Normalerweise verwenden Sie nur einen Vektor, es sei denn, die Größe ist fest oder hat ein angemessenes Maximum.
Justin
0

Anstatt zu versuchen, c struct in ein NSArray einzufügen, können Sie sie als ac-Array von Strukturen in NSData oder NSMutableData einfügen. Um auf sie zuzugreifen, würden Sie das tun

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

oder dann einstellen

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

Für Ihre Struktur können Sie ein Attribut hinzufügen objc_boxableund mithilfe der @()Syntax Ihre Struktur in eine NSValue-Instanz einfügen, ohne Folgendes aufzurufen 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)];
Andrew Romanov
quelle
-2

Ein Obj C-Objekt ist nur eine C-Struktur mit einigen hinzugefügten Elementen. Erstellen Sie einfach eine benutzerdefinierte Klasse, und Sie erhalten den Typ der C-Struktur, den ein NSArray benötigt. Jede C-Struktur, die nicht die zusätzliche Cruft hat, die ein NSObject in seiner C-Struktur enthält, ist für ein NSArray unverdaulich.

Wenn Sie NSData als Wrapper verwenden, wird möglicherweise nur eine Kopie der Strukturen und nicht der ursprünglichen Strukturen gespeichert, wenn dies für Sie einen Unterschied darstellt.

hotpaw2
quelle
-3

Sie können andere NSObject-Klassen als die C-Strukturen zum Speichern von Informationen verwenden. Und Sie können dieses NSObject einfach in NSArray speichern.

Satya
quelle