Как обычно хранить c-структуры в файле NSArray
? Достоинства, недостатки, обработка памяти?
Примечательно, в чем разница между valueWithBytes
и valueWithPointer
- поднятый Джастином и сомом ниже.
Вот ссылка на обсуждение Apple valueWithBytes:objCType:
для будущих читателей ...
Для нестандартного мышления и большего внимания к производительности Evgen поднял вопрос об использовании STL::vector
в C ++ .
(Возникает интересный вопрос: есть ли быстрая библиотека c, похожая на, STL::vector
но намного более легкая, которая позволяет минимально «аккуратно обрабатывать массивы» ...?)
Итак, исходный вопрос ...
Например:
typedef struct _Megapoint {
float w,x,y,z;
} Megapoint;
Итак: каков нормальный, лучший и идиоматический способ хранения такой собственной структуры в анкете NSArray
, и как вы обрабатываете память в этой идиоме?
Обратите внимание, что я специально ищу обычную идиому для хранения структур. Конечно, можно было бы избежать этой проблемы, создав новый маленький класс. Однако я хочу знать, как обычная идиома для фактического размещения структур в массиве, спасибо.
Кстати, вот подход NSData, который возможно? не лучший ...
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];
Кстати, в качестве ориентира и благодаря Джаррету Харди, вот как хранить CGPoints
и подобные вещи в NSArray
:
NSArray *points = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
[NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
nil];
(см. Как я могу добавить объекты CGPoint в NSArray простым способом? )
Ответы:
NSValue поддерживает не только структуры CoreGraphics - вы также можете использовать его для себя. Я бы рекомендовал сделать это, поскольку класс, вероятно, легче, чем
NSData
для простых структур данных.Просто используйте выражение вроде следующего:
[NSValue valueWithBytes:&p objCType:@encode(Megapoint)];
И чтобы вернуть значение:
Megapoint p; [value getValue:&p];
источник
p
, а не указатель на нее.@encode
Директива содержит всю информацию , необходимую о том , как большая структура. Когда вы отпускаетеNSValue
(или когда массив), его копия структуры уничтожается. Если выgetValue:
тем временем использовали, все в порядке. См. Раздел «Использование значений» в «Темах программирования чисел и значений»: developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…@encode
описал бы структуру, но не полностью описал бы указанные данные, которые действительно могут измениться.NSValue
автоматически освобождаться память структуры при ее освобождении? В документации по этому поводу немного неясно.NSValue
владеет данными, которые он копирует в себя, и мне не нужно беспокоиться об их освобождении (под ARC)?NSValue
на самом деле не выполняет никакого «управления памятью» как таковое - вы можете думать об этом как о внутренней копии значения структуры. Если бы структура содержала вложенные указатели, например,NSValue
не знала бы, что нужно освобождать, копировать или делать с ними что-либо - она оставила бы их нетронутыми, скопировав адрес как есть.Я бы посоветовал вам придерживаться этого
NSValue
маршрута, но если вы действительно хотите хранить простыеstruct
типы данных в своем массиве NSArray (и других объектах коллекции в Какао), вы можете сделать это, хотя и косвенно, используя Core Foundation и бесплатный мост. .CFArrayRef
(и его изменяемый аналогCFMutableArrayRef
) предоставляют разработчику большую гибкость при создании объекта массива. См. Четвертый аргумент назначенного инициализатора:CFArrayRef CFArrayCreate ( CFAllocatorRef allocator, const void **values, CFIndex numValues, const CFArrayCallBacks *callBacks );
Это позволяет вам запросить, чтобы
CFArrayRef
объект использовал процедуры управления памятью Core Foundation, вообще никакие, или даже ваши собственные процедуры управления памятью.Обязательный пример:
// 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];
В приведенном выше примере полностью игнорируются проблемы, связанные с управлением памятью. Структура
myStruct
создается в стеке и, следовательно, уничтожается по завершении функции - массив будет содержать указатель на объект, которого больше нет. Вы можете обойти это, используя свои собственные процедуры управления памятью - поэтому вам предоставляется такая опция - но тогда вам придется проделать тяжелую работу по подсчету ссылок, выделению памяти, ее освобождению и так далее.Я бы не рекомендовал это решение, но оставлю его здесь на случай, если он заинтересует кого-то еще. :-)
Здесь демонстрируется использование вашей структуры, выделенной в куче (вместо стека):
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];
источник
struct
, вы, конечно, могли бы выделить его один раз и не освобождать его в будущем. Я включил пример этого в свой отредактированный ответ. Кроме того, это не было опечаткойmyStruct
, поскольку это была структура, размещенная в стеке, в отличие от указателя на структуру, размещенную в куче.Аналогичный метод добавления структуры c - сохранить указатель и отменить ссылку на указатель таким образом;
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];
источник
если вы чувствуете себя занудным или вам действительно нужно создать много классов: иногда бывает полезно динамически создать класс objc (ссылка:)
class_addIvar
. таким образом вы можете создавать произвольные классы objc из произвольных типов. вы можете указать поле за полем или просто передать информацию о структуре (но это практически повторяет NSData). иногда полезно, но для большинства читателей это скорее «забавный факт».вы можете вызвать class_addIvar и добавить переменную экземпляра Megapoint в новый класс, или вы можете синтезировать вариант objc класса Megapoint во время выполнения (например, переменную экземпляра для каждого поля Megapoint).
первый эквивалент скомпилированного класса objc:
@interface MONMegapoint { Megapoint megapoint; } @end
последний эквивалентен скомпилированному классу objc:
@interface MONMegapoint { float w,x,y,z; } @end
после того, как вы добавили ivars, вы можете добавлять / синтезировать методы.
для чтения сохраненных значений на принимающей стороне используйте синтезированные методы,
object_getInstanceVariable
илиvalueForKey:
(которые часто преобразуют эти скалярные переменные экземпляра в представления NSNumber или NSValue).Кстати: все полученные ответы полезны, некоторые лучше / хуже / недействительны в зависимости от контекста / сценария. конкретные потребности, касающиеся памяти, скорости, простоты обслуживания, простоты передачи или архивирования и т. д., определят, что лучше всего для данного случая ... но не существует «идеального» решения, которое было бы идеальным во всех отношениях. не существует «лучшего способа поместить c-структуру в массив NSArray», просто «лучший способ поместить c-структуру в массив NSArray для конкретного сценария, случая или набора требований », который у вас был бы указать.
кроме того, NSArray - это обычно многоразовый интерфейс массива для типов с размером указателя (или меньшего размера), но есть и другие контейнеры, которые лучше подходят для c-структур по многим причинам (std :: vector является типичным выбором для c-структур).
источник
std::vector
(например), который больше подходит для хранения типов, структур и классов C / C ++, чем NSArray. Используя NSArray типов NSValue, NSData или NSDictionary, вы теряете много безопасности типов при добавлении тонны распределений и накладных расходов времени выполнения. Если вы хотите придерживаться C, они обычно используют malloc и / или массивы в стеке ... ноstd::vector
скрывают от вас большинство сложностей.было бы лучше использовать сериализатор objc для бедняков, если вы делитесь этими данными между несколькими abis / архитектурами:
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; /* ... */
... особенно если структура может меняться со временем (или в зависимости от целевой платформы). это не так быстро, как другие варианты, но менее вероятно, что он сломается в некоторых условиях (которые вы не указали как важные или нет).
если сериализованное представление не выходит из процесса, то размер / порядок / выравнивание произвольных структур не должны изменяться, и есть варианты, которые проще и быстрее.
в любом случае вы уже добавляете объект с подсчетом ссылок (по сравнению с NSData, NSValue), поэтому ... создание класса objc, который содержит Megapoint, во многих случаях является правильным ответом.
источник
Я предлагаю вам использовать std :: vector или std :: list для типов C / C ++, потому что сначала он просто быстрее, чем NSArray, а во-вторых, если вам не хватит скорости - вы всегда можете создать свой собственный распределители для контейнеров STL и сделать их еще быстрее. Все современные мобильные движки для игр, физики и звука используют контейнеры STL для хранения внутренних данных. Просто потому, что они очень быстрые.
Если это не для вас - есть хорошие ответы от парней о NSValue - думаю, это наиболее приемлемо.
источник
Megapoint pt[8];
- это вариант в C ++ и специализированных контейнерах C ++ (например,std::array
) - также обратите внимание, что в примере не добавляется специальное выравнивание (было выбрано 16 байтов потому что это размер Megapoint). (продолжение)std::vector
добавит к этому крошечные накладные расходы и одно распределение (если вы знаете, какой размер вам понадобится) ... но это ближе к металлу, чем требуется более чем в 99,9% случаев. обычно вы просто используете вектор, если размер не является фиксированным или имеет разумный максимум.Вместо того, чтобы пытаться поместить c-структуру в NSArray, вы можете поместить их в NSData или NSMutableData как массив структур ac. Чтобы получить к ним доступ, вам нужно сделать
const struct MyStruct * theStruct = (const struct MyStruct*)[myData bytes]; int value = theStruct[2].integerNumber;
или установить тогда
struct MyStruct * theStruct = (struct MyStruct*)[myData mutableBytes]; theStruct[2].integerNumber = 10;
источник
Хотя использование NSValue отлично работает для хранения структур в виде объекта Obj-C, вы не можете кодировать NSValue, содержащее структуру, с помощью NSArchiver / NSKeyedArchiver. Вместо этого вам нужно кодировать отдельные элементы структуры ...
См. Руководство по программированию архивов и сериализации Apple> Структуры и битовые поля
источник
Для своей структуры вы можете добавить атрибут
objc_boxable
и использовать@()
синтаксис для помещения вашей структуры в экземпляр NSValue без вызова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)];
источник
Объект Obj C - это просто структура C с некоторыми добавленными элементами. Так что просто создайте собственный класс, и у вас будет тип структуры C, который требуется для NSArray. Любая структура C, которая не имеет лишнего мусора, который NSObject включает в свою структуру C, будет неперевариваемой для NSArray.
Использование NSData в качестве оболочки может хранить только копию структур, а не исходные структуры, если это имеет для вас значение.
источник
Вы можете использовать классы NSObject, отличные от C-Structures, для хранения информации. И вы можете легко сохранить этот NSObject в NSArray.
источник