Jaki jest typowy sposób przechowywania struktur c w pliku NSArray
? Zalety, wady, obsługa pamięci?
W szczególności, jaka jest różnica między valueWithBytes
a valueWithPointer
- podniesionymi przez justina i suma poniżej.
Oto link do dyskusji Apple valueWithBytes:objCType:
dla przyszłych czytelników ...
Jeśli chodzi o myślenie poboczne i przyjrzenie się wydajności, Evgen poruszył kwestię używania STL::vector
w C ++ .
(To rodzi interesujący problem: czy istnieje szybka biblioteka c, STL::vector
podobna do tej, która jest dużo lżejsza, która pozwala na minimalną „uporządkowaną obsługę tablic”…?)
Więc oryginalne pytanie ...
Na przykład:
typedef struct _Megapoint {
float w,x,y,z;
} Megapoint;
Więc: jaki jest normalny, najlepszy, idiomatyczny sposób przechowywania własnej struktury NSArray
w takim idiomie i jak radzisz sobie z pamięcią w tym idiomie?
Zwróć uwagę, że szczególnie szukam zwykłego idiomu do przechowywania struktur. Oczywiście można by uniknąć tego problemu, tworząc nową małą klasę. Jednak chcę wiedzieć, jak zwykły idiom faktycznie umieszcza struktury w tablicy, dzięki.
A tak przy okazji, oto podejście NSData, które może być? nie najlepiej ...
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];
BTW jako punkt odniesienia i dzięki Jarret Hardie, oto jak przechowywać CGPoints
i podobne w NSArray
:
NSArray *points = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
[NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
nil];
(zobacz W jaki sposób mogę łatwo dodawać obiekty CGPoint do NSArray? )
Odpowiedzi:
NSValue obsługuje nie tylko struktury CoreGraphics - możesz go również używać do własnych celów. Poleciłbym to zrobić, ponieważ klasa jest prawdopodobnie lżejsza niż w
NSData
przypadku prostych struktur danych.Po prostu użyj wyrażenia podobnego do następującego:
[NSValue valueWithBytes:&p objCType:@encode(Megapoint)];
Aby odzyskać wartość:
Megapoint p; [value getValue:&p];
źródło
p
, a nie wskaźnik do niej.@encode
Dyrektywa zawiera wszystkie niezbędne informacje o tym, jak duży jest struktura. Kiedy zwolniszNSValue
(lub gdy zrobi to tablica), jej kopia struktury zostanie zniszczona. Jeśli używałeśgetValue:
w międzyczasie, nic ci nie jest. Zobacz sekcję „Używanie wartości” w „Tematy programowania liczb i wartości”: developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…@encode
opisałbyś strukturę tym wskaźnikiem, ale nie w pełni opisałby wskazywane dane, które rzeczywiście mogłyby się zmienić.NSValue
automatycznie zwolni pamięć struktury po jej zwolnieniu? Dokumentacja jest trochę niejasna w tym zakresie.NSValue
właścicielem danych, które kopiuje do siebie i nie muszę się martwić o ich zwolnienie (w ramach ARC)?NSValue
tak naprawdę nie zajmuje się „zarządzaniem pamięcią” jako takim - możesz myśleć o tym jak o posiadaniu wewnętrznej kopii wartości struktury. Gdyby struktura zawierała na przykład zagnieżdżone wskaźniki,NSValue
nie wiedziałaby, jak zwolnić, skopiować lub cokolwiek z nimi zrobić - pozostawiłoby je nietknięte, kopiując adres w takiej postaci, w jakiej jest.Sugerowałbym trzymanie się
NSValue
trasy, ale jeśli naprawdę chcesz przechowywać zwykłestruct
typy danych ol w swoim NSArray (i innych obiektach kolekcji w Cocoa), możesz to zrobić - choć pośrednio, używając Core Foundation i bezpłatnego mostkowania .CFArrayRef
(i jego zmienny odpowiednikCFMutableArrayRef
) zapewniają programiście większą elastyczność podczas tworzenia obiektu tablicy. Zobacz czwarty argument wyznaczonego inicjatora:CFArrayRef CFArrayCreate ( CFAllocatorRef allocator, const void **values, CFIndex numValues, const CFArrayCallBacks *callBacks );
Pozwala to zażądać, aby
CFArrayRef
obiekt korzystał z procedur zarządzania pamięcią Core Foundation, bez żadnych lub nawet z własnych procedur zarządzania pamięcią.Obowiązkowy przykład:
// 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];
Powyższy przykład całkowicie ignoruje problemy związane z zarządzaniem pamięcią. Struktura
myStruct
jest tworzona na stosie i dlatego jest niszczona po zakończeniu funkcji - tablica będzie zawierała wskaźnik do obiektu, którego już nie ma. Możesz obejść ten problem, używając własnych procedur zarządzania pamięcią - stąd dlaczego ta opcja jest dostępna - ale wtedy musisz wykonać ciężką pracę polegającą na liczeniu odwołań, alokowaniu pamięci, zwalnianiu jej i tak dalej.Nie polecałbym tego rozwiązania, ale zatrzymam je tutaj na wypadek, gdyby zainteresował go ktoś inny. :-)
Używanie twojej struktury jako przydzielonej na stercie (zamiast stosu) jest pokazane tutaj:
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];
źródło
struct
, z pewnością mógłbyś go raz przydzielić i nie zwolnić w przyszłości. Podałem tego przykład w mojej zredagowanej odpowiedzi. Nie była to również literówkamyStruct
, ponieważ była to struktura przydzielona na stosie, różniąca się od wskaźnika do struktury przydzielonej na stercie.Podobną metodą dodawania struktury c jest przechowywanie wskaźnika i usuwanie referencji ze wskaźnika w ten sposób;
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];
źródło
jeśli czujesz się oszołomiony lub naprawdę masz wiele klas do stworzenia: czasami warto dynamicznie skonstruować klasę objc (ref:)
class_addIvar
. w ten sposób możesz tworzyć dowolne klasy objc z dowolnych typów. możesz określić pole po polu lub po prostu przekazać informacje o strukturze (ale to praktycznie replikuje NSData). czasem przydatne, ale prawdopodobnie bardziej „zabawny fakt” dla większości czytelników.możesz wywołać class_addIvar i dodać zmienną instancji Megapoint do nowej klasy, lub możesz zsyntetyzować wariant objc klasy Megapoint w czasie wykonywania (np. zmienną instancji dla każdego pola Megapoint).
pierwsza jest równoważna skompilowanej klasie objc:
@interface MONMegapoint { Megapoint megapoint; } @end
ta ostatnia jest odpowiednikiem skompilowanej klasy objc:
@interface MONMegapoint { float w,x,y,z; } @end
po dodaniu ivars możesz dodać / zsyntetyzować metody.
aby odczytać wartości przechowywane po stronie odbierającej, użyj zsyntetyzowanych metod
object_getInstanceVariable
lubvalueForKey:
(które często konwertują te skalarne zmienne instancji na reprezentacje NSNumber lub NSValue).btw: wszystkie otrzymane odpowiedzi są przydatne, niektóre są lepsze / gorsze / nieprawidłowe w zależności od kontekstu / scenariusza. specyficzne potrzeby dotyczące pamięci, szybkości, łatwości w utrzymaniu, łatwości przenoszenia lub archiwizacji itp. decydują o tym, które jest najlepsze w danym przypadku ... ale nie ma „idealnego” rozwiązania, które byłoby idealne pod każdym względem. nie ma „najlepszego sposobu umieszczenia struktury c w tablicy NSArray”, po prostu „najlepszego sposobu umieszczenia struktury c w tablicy NSArray dla określonego scenariusza, przypadku lub zestawu wymagań ” - co byś miał sprecyzować.
ponadto NSArray jest generalnie interfejsem tablicowym wielokrotnego użytku dla typów o rozmiarze wskaźnika (lub mniejszych), ale istnieją inne kontenery, które są lepiej dostosowane do struktur c z wielu powodów (typowym wyborem dla struktur c jest std :: vector).
źródło
std::vector
(na przykład) jest bardziej odpowiedni do przechowywania typów, struktur i klas C / C ++ niż NSArray. Używając NSArray typów NSValue, NSData lub NSDictionary, tracisz dużo bezpieczeństwa typów, dodając mnóstwo alokacji i narzutów w czasie wykonywania. Jeśli chcesz trzymać się C, zwykle używają malloc i / lub tablic na stosie ... alestd::vector
ukrywają przed tobą większość komplikacji.najlepiej byłoby użyć serializatora objc biedaka, jeśli udostępniasz te dane na wielu abis / architekturach:
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; /* ... */
... zwłaszcza jeśli struktura może się zmieniać w czasie (lub w zależności od platformy docelowej). nie jest tak szybki jak inne opcje, ale jest mniej prawdopodobne, że zepsuje się w niektórych warunkach (które nie zostały określone jako ważne lub nie).
jeśli serializowana reprezentacja nie kończy procesu, to rozmiar / kolejność / wyrównanie dowolnych struktur nie powinno się zmieniać, a istnieją opcje, które są prostsze i szybsze.
w obu przypadkach już dodajesz obiekt zliczany przez odniesienie (w porównaniu do NSData, NSValue), więc ... utworzenie klasy objc, która przechowuje Megapoint, jest w wielu przypadkach właściwą odpowiedzią.
źródło
Proponuję użyć std :: vector lub std :: list dla typów C / C ++, ponieważ na początku jest po prostu szybszy niż NSArray, a po drugie, jeśli nie będzie dla Ciebie wystarczającej prędkości - zawsze możesz stworzyć własną podzielniki do kontenerów STL i uczynić je jeszcze szybszymi. Wszystkie nowoczesne silniki gier mobilnych, fizyki i audio używają kontenerów STL do przechowywania danych wewnętrznych. Tylko dlatego, że naprawdę szybko.
Jeśli to nie dla ciebie - są dobre odpowiedzi od facetów na temat NSValue - myślę, że jest to najbardziej akceptowalne.
źródło
Megapoint pt[8];
- jest to opcja w c ++ i wyspecjalizowanych kontenerach c ++ (np.std::array
) - również zauważ, że przykład nie dodaje specjalnego wyrównania (wybrano 16 bajtów ponieważ jest wielkości Megapoint). (ciąg dalszy)std::vector
doda do tego niewielką ilość narzutów i jedną alokację (jeśli znasz rozmiar, którego potrzebujesz) ... ale jest to bliższe metalowi niż potrzebuje ponad 99,9% przypadków. zazwyczaj używałbyś po prostu wektora, chyba że rozmiar jest stały lub ma rozsądne maksimum.Zamiast próbować umieścić c struct w NSArray, możesz umieścić je w NSData lub NSMutableData jako tablicę ac struktur. Aby uzyskać do nich dostęp, wystarczy
const struct MyStruct * theStruct = (const struct MyStruct*)[myData bytes]; int value = theStruct[2].integerNumber;
albo wtedy ustawić
struct MyStruct * theStruct = (struct MyStruct*)[myData mutableBytes]; theStruct[2].integerNumber = 10;
źródło
Podczas gdy użycie NSValue działa dobrze do przechowywania struktur jako obiektu Obj-C, nie można zakodować NSValue zawierającej strukturę za pomocą NSArchiver / NSKeyedArchiver. Zamiast tego musisz zakodować poszczególne elementy składowe struktury ...
Zobacz Podręcznik programowania archiwów i serializacji Apple> Struktury i pola bitów
źródło
Do swojej struktury możesz dodać atrybut
objc_boxable
i użyć@()
składni, aby umieścić swoją strukturę w instancji NSValue bez wywoływaniavalueWithBytes: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)];
źródło
Obiekt Obj C to po prostu struktura C z kilkoma dodanymi elementami. Więc po prostu utwórz niestandardową klasę, a otrzymasz typ struktury C, którego wymaga NSArray. Każda struktura C, która nie ma dodatkowej skorupy, jaką NSObject zawiera w swojej strukturze C, będzie niestrawna dla NSArray.
Używanie NSData jako opakowania może oznaczać tylko przechowywanie kopii struktur, a nie oryginalnych struktur, jeśli ma to dla Ciebie znaczenie.
źródło
Do przechowywania informacji można używać klas NSObject innych niż C-Structures. I możesz łatwo przechowywać ten obiekt NSObject w NSArray.
źródło