Какой лучший способ поместить c-struct в NSArray?

Какой обычный способ хранить c-структуры в NSArray? Преимущества, недостатки, обработка памяти?

Примечательно, какая разница между valueWithBytes и valueWithPointer - подняты valueWithPointer и Сом ниже.

Здесь ссылка на обсуждение Apple valueWithBytes:objCType: для будущих читателей...

Для некоторого бокового мышления и больше внимания к производительности, Евгений поднял вопрос об использовании 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 простым способом?)

Ответ 1

NSValue не только поддерживает структуры CoreGraphics - вы также можете использовать его для своих собственных. Я бы порекомендовал сделать это, так как класс, вероятно, легче, чем NSData для простых структур данных.

Просто используйте выражение, подобное следующему:

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

И чтобы вернуть значение:

Megapoint p;
[value getValue:&p];

Ответ 2

Я предлагаю вам придерживаться маршрута NSValue, но если вы действительно хотите хранить простые 'ol struct типы данных в ваш NSArray (и другие объекты коллекции в Cocoa), вы можете сделать это - хотя бы косвенно, используя 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];

Ответ 3

Аналогичным методом добавления c struct является сохранение указателя и де-ссылка на указатель как таковой;

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

было бы лучше использовать сериализатор objc для бедных людей, если вы делитесь этими данными на нескольких abis/architecture:

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

... особенно если структура может меняться со временем (или с помощью целевой платформы). это не так быстро, как другие варианты, но менее вероятно, что он сломается в некоторых условиях (которые вы не указали как важные или нет).

Если сериализованное представление не выходит из процесса, то размер/порядок/выравнивание произвольных структур не должны меняться, и есть опции, которые проще и быстрее.

в любом случае вы уже добавляете объект с подсчетом ref (по сравнению с NSData, NSValue), поэтому... создание класса objc, который содержит Megapoint, является правильным ответом во многих случаях.

Ответ 5

если вы чувствуете уродливость или действительно имеете много классов для создания: иногда полезно динамически строить класс objc (ref: 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).

btw: все ответы, которые вы получили, полезны, некоторые из них лучше/хуже/недействительны в зависимости от контекста/сценария. конкретные потребности в памяти, скорость, удобство в обслуживании, легкость передачи или архивирования и т.д. определит, что лучше всего подходит для данного случая... но нет идеального решения, которое идеально подходит во всех отношениях. нет "лучшего способа поставить c-struct в NSArray", просто "лучший способ поместить c-struct в NSArray для конкретного сценария, случая или набора требований" - что вы бы для указания.

Кроме того, NSArray является обычно многоразовым интерфейсом массива для типов (или меньших) указателя, но существуют другие контейнеры, которые лучше подходят для c-struct по многим причинам (std::vector является типичным выбором для c-structs).

Ответ 6

Я предлагаю вам использовать std::vector или std:: list для типов C/С++, потому что сначала он быстрее, чем NSArray, а во-вторых, если вам будет недостаточно скорости - вы всегда можете создать ваши собственные распределители для контейнеров STL и сделать их еще быстрее. Все современные мобильные движки Game, Physics и Audio используют контейнеры STL для хранения внутренних данных. Просто потому, что они действительно быстры.

Если это не для вас - есть хорошие ответы от парней о NSValue - я думаю, что это наиболее приемлемо.

Ответ 7

Вместо того, чтобы ставить c struct в NSArray, вы можете поместить их в NSData или NSMutableData в виде массива c структур. Чтобы получить к ним доступ, выполните

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;

Ответ 8

При использовании NSValue отлично подходит для хранения структур как объекта Obj-C, вы не можете кодировать NSValue, содержащий структуру с NSArchiver/NSKeyedArchiver. Вместо этого вам нужно кодировать отдельные элементы структуры...

См. Руководство по программированию и сериализации Apple Archi > Структуры и битовые поля

Ответ 9

Для вашей структуры вы можете добавить атрибут 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)];

Ответ 10

Вы можете хранить все, что реализует протокол NSObject в NSArray. Обычно я просто создаю свой собственный класс, который обертывает структуру C и наследует NSObject (и поэтому автоматически реализует протокол NSObject), а затем просто используйте это.

Ответ 11

Объект Obj C - это просто структура C с добавленными элементами. Поэтому просто создайте пользовательский класс, и у вас будет тип структуры C, который требуется NSArray. Любая C-структура, которая не имеет дополнительного крути, который NSObject включает в свою C-структуру, будет неудобоваримой для NSArray.

Использование NSData в качестве оболочки может содержать только копию структур, а не исходных структур, если это имеет для вас значение.

Ответ 12

Вы можете использовать классы NSObject, отличные от C-Структур, для хранения информации. И вы можете легко сохранить этот NSObject в NSArray.