Was ist der übliche Weg, um C-Strukturen in einem zu speichern NSArray
? Vor- und Nachteile, Speicherhandhabung?
Insbesondere, was ist der Unterschied zwischen valueWithBytes
und 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::vector
in C ++ angesprochen .
(Das wirft ein interessantes Problem auf: Gibt es eine schnelle c-Bibliothek, die nicht anders, STL::vector
aber 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 CGPoints
und ä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? )
Antworten:
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
NSData
für einfache Datenstrukturen.Verwenden Sie einfach einen Ausdruck wie den folgenden:
Und um den Wert wieder herauszuholen:
quelle
p
, keinen Zeiger darauf. Die@encode
Richtlinie enthält alle erforderlichen Informationen darüber, wie groß die Struktur ist. Wenn Sie das freigebenNSValue
(oder wenn das Array dies tut), wird seine Kopie der Struktur zerstört. Wenn SiegetValue:
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/…@encode
die Struktur mit diesem Zeiger beschreiben, aber die Daten, auf die verwiesen wird, nicht vollständig beschreiben, was sich tatsächlich ändern könnte.NSValue
der Speicher der Struktur automatisch frei, wenn die Zuordnung aufgehoben wird? Die Dokumentation ist dazu etwas unklar.NSValue
Um 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)?NSValue
fü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 enthaltenNSValue
wü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.Ich würde vorschlagen, dass Sie sich an die
NSValue
Route halten, aber wenn Sie wirklich einfachestruct
Datentypen 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ückCFMutableArrayRef
) bieten dem Entwickler mehr Flexibilität beim Erstellen eines Array-Objekts. Siehe das vierte Argument des bezeichneten Initialisierers:Auf diese Weise können Sie anfordern, dass das
CFArrayRef
Objekt die Speicherverwaltungsroutinen von Core Foundation verwendet, überhaupt keine oder sogar Ihre eigenen Speicherverwaltungsroutinen.Obligatorisches Beispiel:
Das obige Beispiel ignoriert die Probleme in Bezug auf die Speicherverwaltung vollständig. Die Struktur
myStruct
wird 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:
quelle
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ürmyStruct
, da es sich um eine auf dem Stapel zugewiesene Struktur handelte, die sich von einem Zeiger auf eine auf dem Heap zugewiesene Struktur unterschied.Eine ähnliche Methode zum Hinzufügen von c struct besteht darin, den Zeiger zu speichern und den Zeiger so zu de-referenzieren.
quelle
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".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:
Letzteres entspricht der kompilierten objc-Klasse:
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_getInstanceVariable
odervalueForKey:
(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).
quelle
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::vector
verbergen jedoch die meisten Komplikationen vor Ihnen.Es ist am besten, den Objekt-Serializer des armen Mannes zu verwenden, wenn Sie diese Daten über mehrere Abis / Architekturen hinweg teilen:
... 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.
quelle
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.
quelle
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)std::vector
Dies 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.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
oder dann einstellen
quelle
Während die Verwendung eines NSValue zum Speichern von Strukturen als Obj-C-Objekt einwandfrei funktioniert, können Sie einen NSValue, der eine Struktur enthält, nicht mit NSArchiver / NSKeyedArchiver codieren. Stattdessen müssen Sie einzelne Strukturelemente codieren ...
Siehe Apples Programmierhandbuch für Archive und Serialisierungen> Strukturen und Bitfelder
quelle
Für Ihre Struktur können Sie ein Attribut hinzufügen
objc_boxable
und mithilfe der@()
Syntax Ihre Struktur in eine NSValue-Instanz einfügen, ohne Folgendes aufzurufenvalueWithBytes:objCType:
:quelle
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.
quelle
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.
quelle