wake-up-neo.com

Was ist der beste Weg, eine c-struct in einen NSArray zu setzen?

Was ist der übliche Weg, c-Strukturen in einem NSArray zu speichern? Vorteile, Nachteile, Speicherhandling?

Was ist der Unterschied zwischen valueWithBytes und valueWithPointer -, die von Justin und Wels aufgezogen werden?.

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

Für etwas Querdenken und mehr für die Performance hat Evgen die Verwendung von STL::vector in C++ zur Sprache gebracht.

(Das wirft ein interessantes Problem auf: Gibt es eine schnelle c-Bibliothek, die STL::vector nicht unähnlich ist, aber viel leichter ist, was eine minimale "ordentliche Handhabung von Arrays" ermöglicht ...?)}

Also die ursprüngliche Frage ...

Zum Beispiel:

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

Also: Was ist der normale, beste, idiomatische Weg, um seine eigene Struktur in einer NSArray zu speichern, und wie handhabt man das Gedächtnis in dieser Sprache?

Bitte beachten Sie, dass ich speziell nach der üblichen Sprache für die Aufbewahrung von Strukturen suche. Natürlich könnte man das Problem umgehen, indem man eine neue kleine Klasse erstellt. Ich möchte jedoch wissen, wie die gebräuchliche Redewendung dafür ist, structs tatsächlich in ein Array zu setzen, danke.

BTW hier ist 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 an Jarret Hardie, wie man CGPoints und Ähnliches in einem NSArray speichert:

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

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

84
Fattie

NSValue unterstützt nicht nur CoreGraphics-Strukturen - Sie können es auch für Ihre eigenen verwenden. Ich würde dies empfehlen, da die Klasse für einfache Datenstrukturen wahrscheinlich leichter ist als NSData.

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

Ich würde vorschlagen, dass Sie sich an die NSValue route halten, aber wenn Sie wirklich nur "ol struct" -Datentypen in Ihrem NSArray (und anderen Sammlungsobjekten in Cocoa) speichern möchten, können Sie dies tun - wenn auch indirekt mit Core Gründung und gebührenfreie Überbrückung .

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

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

Auf diese Weise können Sie anfordern, dass das CFArrayRef-Objekt die Speicherverwaltungsroutinen von Core Foundation oder gar keine oder sogar Ihre eigenen Speicherverwaltungsroutinen verwendet.

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

Im obigen Beispiel werden die Probleme in Bezug auf die Speicherverwaltung vollständig ignoriert. Die Struktur myStruct wird auf dem Stack erstellt und daher zerstört, wenn die Funktion endet. Das Array enthält einen Zeiger auf ein Objekt, das nicht mehr vorhanden ist. Sie können dies umgehen, indem Sie Ihre eigenen Routinen für die Speicherverwaltung verwenden - also warum Ihnen diese Option zur Verfügung gestellt wird. Dann müssen Sie jedoch die Referenzarbeit zählen, Speicher zuweisen, freigeben und so weiter.

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


Die Verwendung Ihrer Struktur als auf dem Heap zugewiesen (anstelle des Stapels) wird hier veranschaulicht:

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

Eine ähnliche Methode zum Hinzufügen von c struct besteht darin, den Zeiger zu speichern und den Zeiger auf diese Weise zu entfernen.

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
Keynes

wenn Sie sich nerdig fühlen oder wirklich viele Klassen erstellen müssen: Manchmal ist es nützlich, eine Objc-Klasse (ref: class_addIvar) dynamisch zu erstellen. 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 ist jedoch eine praktische Replikation von 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 zur Laufzeit eine Objektvariante der Megapoint-Klasse synthetisieren (z. B. eine Instanzvariable für jedes Megapoint-Feld).

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.

um die gespeicherten Werte auf der empfangenden Seite zu lesen, verwenden Sie Ihre synthetisierten Methoden object_getInstanceVariable oder 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 besser/schlechter/ungültiger, je nach Kontext/Szenario. Die spezifischen Anforderungen in Bezug auf Speicher, Geschwindigkeit, Wartungsfreundlichkeit, einfache Übertragung oder Archivierung usw. bestimmen, welche Lösung für einen bestimmten Fall am besten ist. Es gibt jedoch keine "perfekte" Lösung, die in jeder Hinsicht ideal ist. Es gibt keine "beste Möglichkeit, eine c-struct in einem NSArray zu setzen", sondern lediglich "die beste Methode, um eine c-struct in einem NSArray für ein bestimmtes Szenario, einen Fall oder eine Reihe von Anforderungen" zu setzen Sie müssten angeben.

darüber hinaus ist NSArray eine allgemein wiederverwendbare Array-Schnittstelle für Zeigertypen (oder kleinere), es gibt jedoch andere Container, die aus vielen Gründen für c-structs besser geeignet sind (std :: vector ist eine typische Wahl für c-structs).

3
justin

es ist am besten, den Objc-Serializer des Armen zu verwenden, wenn Sie diese Daten für mehrere Abis/Architekturen freigeben:

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 (oder durch gezielte Plattform) ändern kann. Es ist nicht so schnell wie andere Optionen, aber es ist weniger wahrscheinlich, dass es unter bestimmten Umständen (die Sie nicht als wichtig eingestuft haben oder nicht angeben) zerbrechen kann.

wenn die serialisierte Darstellung den Prozess nicht beendet, sollte sich die 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 ref-counted-Objekt hinzu (im Vergleich zu NSData, NSValue). Das Erstellen einer Objektklasse, die Megapoint enthält, ist in vielen Fällen die richtige Antwort.

3
justin

Für Ihre Struktur können Sie ein Attributobjc_boxable hinzufügen und die Syntax @() verwenden, um Ihre Struktur in die NSValue-Instanz zu kopieren, ohne valueWithBytes:objCType: aufzurufen:

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)];
0
Andrew Romanov

Während die Verwendung eines NSValue zum Speichern von Strukturen als Obj-C-Objekt geeignet ist, können Sie einen NSValue, der eine Struktur enthält, nicht mit NSArchiver/NSKeyedArchiver codieren. Stattdessen müssen Sie einzelne Strukturelemente codieren ...

Siehe Programmierhandbuch für Archive und Serialisierungen von Apple> Strukturen und Bitfelder

0
DarkMatter

Anstatt zu versuchen, c struct in einem NSArray zu platzieren, können Sie sie in ein NSData oder NSMutableData als c-Array von Strukturen 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;
0
Nathan Day

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 wenn zweitens nicht genug Geschwindigkeit für Sie vorhanden ist - Sie können immer Ihren eigenen erstellen Zuteiler für STL-Container und machen sie noch schneller. Alle modernen mobilen Game-, Physics- 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 - ich denke, es ist am akzeptabelsten.

0
Evgen Bodunov