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

89

Как обычно хранить 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 простым способом? )

Жирность
источник
ваш код для преобразования его в NSData должен быть в порядке ... и без утечек памяти ... однако можно также использовать стандартный массив структур C ++ Megapoint p [3];
Swapnil Luktuke
Вы не можете добавить вознаграждение, пока вопрос не истечет два дня назад.
Мэтью Фредерик,
1
Однако valueWithCGPoint недоступен для OSX. Это часть UIKit
lppier
@Ippier valueWithPoint доступен в OS X
Schpaencoder

Ответы:

160

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

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

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

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

Megapoint p;
[value getValue:&p];
Джастин Спар-Саммерс
источник
4
@Joe Blow @Catfish_Man Фактически копирует структуру p, а не указатель на нее. @encodeДиректива содержит всю информацию , необходимую о том , как большая структура. Когда вы отпускаете NSValue(или когда массив), его копия структуры уничтожается. Если вы getValue:тем временем использовали, все в порядке. См. Раздел «Использование значений» в «Темах программирования чисел и значений»: developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Джастин Спар-Саммерс,
1
@Joe Blow В основном правильно, за исключением того, что он не может быть изменен во время выполнения. Вы указываете тип C, который всегда должен быть полностью известен. Если бы он мог стать «больше», ссылаясь на большее количество данных, то вы, вероятно, реализовали бы это с помощью указателя, и этот указатель @encodeописал бы структуру, но не полностью описал бы указанные данные, которые действительно могут измениться.
Джастин Спар-Саммерс,
1
Будет ли NSValueавтоматически освобождаться память структуры при ее освобождении? В документации по этому поводу немного неясно.
devios1
1
Итак, чтобы быть полностью ясным, NSValueвладеет данными, которые он копирует в себя, и мне не нужно беспокоиться об их освобождении (под ARC)?
devios1
1
@devios Правильно. NSValueна самом деле не выполняет никакого «управления памятью» как таковое - вы можете думать об этом как о внутренней копии значения структуры. Если бы структура содержала вложенные указатели, например, NSValueне знала бы, что нужно освобождать, копировать или делать с ними что-либо - она ​​оставила бы их нетронутыми, скопировав адрес как есть.
Джастин Спар-Саммерс,
7

Я бы посоветовал вам придерживаться этого 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];
Успокаивающий инопланетянин
источник
Изменяемые массивы (по крайней мере, в этом случае) создаются в пустом состоянии и, следовательно, не нуждаются в указателе на значения, которые будут храниться внутри. Это отличается от неизменяемого массива, в котором содержимое «замораживается» при создании и, следовательно, значения должны передаваться в процедуру инициализации.
Sedate Alien
@ Джо Блоу: Это отличный аргумент, который вы сделали в отношении управления памятью. Вы правы, если запутались: пример кода, который я опубликовал выше, вызовет загадочные сбои в зависимости от того, когда будет перезаписан стек функции. Я начал подробно рассказывать о том, как можно использовать мое решение, но понял, что переопределяю собственный подсчет ссылок Objective-C. Приношу свои извинения за компактный код - дело не в способностях, а в лени. Нет смысла писать код, который другие не могут прочитать. :)
Sedate Alien
Если бы вы были счастливы «утечь» (не говоря уже о лучшем слове) struct, вы, конечно, могли бы выделить его один раз и не освобождать его в будущем. Я включил пример этого в свой отредактированный ответ. Кроме того, это не было опечаткой myStruct, поскольку это была структура, размещенная в стеке, в отличие от указателя на структуру, размещенную в куче.
Sedate Alien
4

Аналогичный метод добавления структуры 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

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

Джастин
источник
фон людей тоже играет важную роль ... то, как вам нужно использовать эту структуру, часто исключает некоторые возможности. 4 float довольно надежен, но макеты структур слишком сильно различаются в зависимости от архитектуры / компилятора, чтобы использовать непрерывное представление памяти (например, NSData) и ожидать, что оно будет работать. Сериализатор objc для бедняков, вероятно, имеет самое медленное время выполнения, но он наиболее совместим, если вам нужно сохранить / открыть / передать Megapoint на любом устройстве OS X или iOS. По моему опыту, наиболее распространенный способ - просто поместить структуру в класс objc. если вы проходите через все это только для (продолжение)
Justin
(продолжение) Если вы проходите через все эти хлопоты только для того, чтобы избежать изучения нового типа коллекции - тогда вам следует изучить новый тип коллекции =) std::vector(например), который больше подходит для хранения типов, структур и классов C / C ++, чем NSArray. Используя NSArray типов NSValue, NSData или NSDictionary, вы теряете много безопасности типов при добавлении тонны распределений и накладных расходов времени выполнения. Если вы хотите придерживаться C, они обычно используют malloc и / или массивы в стеке ... но std::vectorскрывают от вас большинство сложностей.
Джастин
на самом деле, если вы хотите манипулировать массивом / итерацию, как вы упомянули, stl (часть стандартных библиотек С ++) отлично подходит для этого. у вас есть больше типов на выбор (например, если вставка / удаление важнее, чем время доступа для чтения), и множество существующих способов манипулирования контейнерами. также - это не голая память в C ++ - контейнеры и функции шаблонов распознают типы и проверяются при компиляции - намного безопаснее, чем извлечение произвольной байтовой строки из представлений NSData / NSValue. у них также есть проверка границ и в основном автоматическое управление памятью. (продолжение)
Джастин
(продолжение) если вы ожидаете, что у вас будет много такой низкоуровневой работы, то вы должны просто изучить ее сейчас, но для этого потребуется время. упаковывая все это в представления objc, вы теряете большую производительность и безопасность типов, в то же время заставляя себя писать гораздо больше шаблонного кода для доступа и интерпретации контейнеров и их значений (если «Таким образом, чтобы быть очень конкретным…» - это именно то, что вы хотите сделать).
Джастин
это просто еще один инструмент в вашем распоряжении. могут возникнуть сложности при интеграции objc с c ++, c ++ с objc, c с c ++ или любой из нескольких других комбинаций. добавление языковых функций и использование нескольких языков в любом случае требует небольших затрат. это идет всеми путями. например, время сборки увеличивается при компиляции как objc ++. кроме того, эти источники не так легко повторно использовать в других проектах. Конечно, вы можете заново реализовать языковые функции ... но это не всегда лучшее решение. Интеграция c ++ в проект objc - это нормально, это примерно так же «беспорядочно», как использование источников objc и c в одном проекте. (продолжение
Джастин
3

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

Джастин
источник
@Joe Blow что-то, что выполняет сериализацию. для справки: en.wikipedia.org/wiki/Serialization , parashift.com/c++-faq-lite/serialization.html , а также «Руководство по программированию архивов и сериализаций» Apple.
Джастин
если предположить, что файл xml правильно представляет что-то, тогда да - это одна из распространенных форм удобочитаемой сериализации.
Джастин
0

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

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

Евгений Бодунов
источник
STL - это библиотека, частично входящая в стандартную библиотеку C ++. en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Бодунов
Это интересное заявление. У вас есть ссылка на статью о преимуществах скорости контейнеров STL по сравнению с классами контейнеров какао?
Sedate Alien
вот интересное чтение NSCFArray vs std :: vector: ridiculousfish.com/blog/archives/2005/12/23/array в примере в вашем сообщении, самая большая потеря (обычно) заключается в создании представления объекта objc для каждого элемента (например, , Тип NSValue, NSData или Objc, содержащий Megapoint, требует выделения и вставки в систему с подсчетом ссылок). на самом деле вы могли бы избежать этого, используя подход Sedate Alien к хранению мегапоинтов в специальном массиве CFArray, который использует отдельное резервное хранилище непрерывно выделенных мегапоинтов (хотя ни один пример не иллюстрирует этот подход). (продолжение)
Джастин
но тогда использование NSCFArray по сравнению с вектором (или другим типом stl) повлечет за собой дополнительные накладные расходы на динамическую отправку, дополнительные вызовы функций, которые не являются встроенными, тонну безопасности типов и много шансов для оптимизатора ... статья просто фокусируется на вставке, чтении, прогулке, удалении. скорее всего, вы не получите ничего быстрее, чем 16-байтовый выровненный c-массив Megapoint pt[8];- это вариант в C ++ и специализированных контейнерах C ++ (например, std::array) - также обратите внимание, что в примере не добавляется специальное выравнивание (было выбрано 16 байтов потому что это размер Megapoint). (продолжение)
Джастин
std::vectorдобавит к этому крошечные накладные расходы и одно распределение (если вы знаете, какой размер вам понадобится) ... но это ближе к металлу, чем требуется более чем в 99,9% случаев. обычно вы просто используете вектор, если размер не является фиксированным или имеет разумный максимум.
Джастин
0

Вместо того, чтобы пытаться поместить 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

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

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

Темная материя
источник
0

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

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

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

hotpaw2
источник
-3

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

Сатья
источник