Какой лучший способ поместить c-struct в NSArray? - PullRequest
84 голосов
/ 23 декабря 2010

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

Примечательно, в чем разница между valueWithBytes и 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? )

Ответы [ 10 ]

153 голосов
/ 23 декабря 2010

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

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

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

И чтобы получить значениеотступить:

Megapoint p;
[value getValue:&p];
7 голосов
/ 24 декабря 2010

Я бы посоветовал вам придерживаться маршрута NSValue, но если вы действительно хотите хранить простые типы данных ol '1004 * в вашем 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];
4 голосов
/ 11 июля 2014

Аналогичным способом добавления структуры 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];
3 голосов
/ 24 декабря 2010

, если вы чувствуете себя плохо или действительно хотите создать много классов: иногда полезно динамически создать класс 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-структуру в NSArray», просто «лучший способ поместить c-структуру в NSArray для конкретного сценария, случая или набора требований » -- который вы должны будете указать.

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

3 голосов
/ 24 декабря 2010

было бы лучше использовать сериализатор 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, является правильным ответом во многих случаях.

0 голосов
/ 25 мая 2013

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

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

0 голосов
/ 17 мая 2013

Вместо того, чтобы пытаться поместить структуру 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;
0 голосов
/ 30 декабря 2010

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

0 голосов
/ 29 декабря 2010

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

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

0 голосов
/ 23 декабря 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...