Який найкращий спосіб помістити c-структуру в NSArray?


89

Який звичайний спосіб зберігати 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? )


ваш код для перетворення його в NSData повинен бути чудовим .. і без витоків пам’яті .... однак можна також використовувати стандартний масив C ++ структур Megapoint p [3];
Swapnil Luktuke

Ви не можете додати нагороду, поки запитання не виповниться два дні.
Matthew Frederick

1
valueWithCGPoint недоступний для OSX. Це частина UIKit
lppier

@Ippier valueWithPoint доступний в OS X
Schpaencoder

Відповіді:


159

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 Здебільшого правильний, за винятком того, що він не зможе змінитися під час виконання. Ви вказуєте тип С, який завжди повинен бути повністю відомим. Якби він міг отримати "більший розмір", посилаючись на більше даних, тоді ви, напевно, реалізували б це за допомогою вказівника, і @encodeвін описував би структуру за допомогою цього вказівника, але не в повній мірі описував вказані дані, які справді могли б змінитися.
Джастін Шпар-Саммерс

1
Чи NSValueавтоматично звільниться пам’ять структури, коли вона буде вивільнена? Документація з цього приводу трохи незрозуміла.
devios1

1
Тож, щоб бути повністю зрозумілим, NSValueволодіє даними, які він копіює в себе, і мені не потрібно турбуватися про їх звільнення (за ARC)?
devios1

1
@devios Правильно. NSValueнасправді не робить жодного «управління пам’яттю» як такого - ви можете думати про це як про наявність внутрішньої копії значення структури. Якщо структура, наприклад, містила вкладені вказівники, NSValueне могла б звільнити, скопіювати або щось з ними зробити - це залишило б їх недоторканими, копіюючи адресу як є.
Джастін Шпар-Саммерс

7

Я б запропонував вам дотримуватися NSValueмаршруту, але якщо ви дійсно хочете зберігати звичайні 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];

Змінювані масиви (принаймні в цьому випадку) створюються у порожньому стані, а отже, їм не потрібен вказівник на значення, які слід зберігати всередині. Це відрізняється від незмінного масиву, де вміст "заморожений" при створенні, а отже, значення повинні передаватися в процедуру ініціалізації.
Sedate Alien

@Joe Blow: Це чудова думка щодо управління пам'яттю. Ви маєте рацію в розгубленості: зразок коду, який я розмістив вище, спричинить загадкові збої, залежно від того, коли стек функції буде перезаписаний. Я почав детально обговорювати, як можна використовувати моє рішення, але зрозумів, що я здійснюю власний підрахунок посилань на Objective-C. Прошу вибачення за компактний код - справа не в схильності, а в ліні. Немає сенсу писати код, який інші не можуть прочитати. :)
Sedate Alien

Якби ви були раді "просочитися" (через брак кращого слова) struct, ви, звичайно, могли б виділити його один раз і не звільнити в майбутньому. Я включив приклад цього у свою відредаговану відповідь. Крім того, це не була друкарська помилка myStruct, оскільки це була структура, виділена в стеку, на відміну від вказівника на структуру, виділену в купі.
Sedate Alien

4

Подібний метод для додавання 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];

3

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

До речі: усі отримані вами відповіді корисні, деякі - кращі / гірші / недійсні залежно від контексту / сценарію. конкретні потреби щодо пам’яті, швидкості, простоти обслуговування, простоти передачі чи архівування тощо визначають, що найкраще для конкретного випадку ... але не існує „ідеального” рішення, яке є ідеальним у всіх відношеннях. не існує "найкращого способу розміщення c-структури в NSArray", просто "найкращого способу розміщення c-struct в NSArray для конкретного сценарію, випадку чи набору вимог " - який ви б мали вказати.

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


фони людей також входять у гру ... як вам потрібно використовувати цю структуру, часто усуває деякі можливості. 4 floats є досить надійним, але макети структури занадто сильно різняться залежно від архітектури / компілятора, щоб використовувати суміжне представлення пам'яті (наприклад, NSData) і очікувати його роботи. у серіалізатора objc бідної людини, швидше за все, найменший час виконання, але він є найбільш сумісним, якщо вам потрібно зберегти / відкрити / передати Megapoint на будь-якому пристрої OS X або iOS. З мого досвіду найпоширеніший спосіб - це просто помістити структуру в клас objc. якщо ви переживаєте все це просто (продовження)
justin

(продовження) Якщо ви переживаєте всі ці клопоти лише для того, щоб уникнути вивчення нового типу колекції - тоді вам слід вивчити новий тип колекції =) std::vector(наприклад) більше підходить для проведення типів, структур та класів C / C ++, ніж NSArray. Використовуючи NSArray типів NSValue, NSData або NSDictionary, ви втрачаєте багато захисту від типів, одночасно додаючи масу розподілів та накладні витрати на виконання. Якщо ви хочете дотримуватися C, тоді вони зазвичай використовують malloc та / або масиви в стеку ... але std::vectorприховує від вас більшість ускладнень.
Джастін

насправді, якщо вам потрібна маніпуляція / ітерація масиву, як ви вже згадували - stl (частина стандартних бібліотек c ++) чудово для цього підходить. у вас є на вибір більше типів (наприклад, якщо вставка / видалення важливіше часу доступу для читання), а також безліч існуючих способів маніпулювання контейнерами. також - це не гола пам'ять в c ++ - контейнери та функції шаблону знають тип і перевіряються при компіляції - набагато безпечніше, ніж витягування довільного байтового рядка із подань NSData / NSValue. вони також мають перевірку меж і в основному автоматичне управління пам'яттю. (продовження)
Justin

(продовження) якщо ви очікуєте, що у вас буде багато такої роботи на низькому рівні, то вам слід просто навчитися її зараз - але для вивчення знадобиться час. обертаючи все це в уявленнях objc, ви втрачаєте багато продуктивності та безпеки типу, змушуючи писати набагато більше зразкового коду для доступу та інтерпретації контейнерів та їх значень (якщо "Таким чином, бути дуже конкретним ..." саме те, що ви хочете зробити).
джастін

це просто ще один інструмент у вашому розпорядженні. можуть виникнути ускладнення при інтеграції objc з c ++, c ++ з objc, c з c ++ або будь-якою з кількох інших комбінацій. додавання функцій мови та використання декількох мов у будь-якому випадку має невеликі витрати. це йде всіма шляхами. наприклад, час компіляції збільшується при компіляції як objc ++. крім того, ці джерела не використовуються в інших проектах так легко. Звичайно, ви можете змінити мовні функції ... але це не часто найкраще рішення. інтегрування c ++ в проект objc - це нормально, це приблизно так само брудно, як використання джерел objc та c в одному проекті. (продовження
justin

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.
Justin

якщо припустити, що файл xml правильно представляє щось, тоді так - це одна із поширених форм зручної для читання серіалізації.
Justin

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 проти std :: vector: ridiculousfish.com/blog/archives/2005/12/23/array у прикладі у вашому дописі, найбільшою втратою є (як правило) створення представлення об’єкта objc для кожного елемента (наприклад, , Тип NSValue, NSData або Objc, що містить Megapoint, вимагає розподілу та вставки в систему, що підраховується заново). ви могли б насправді цього уникнути, використовуючи підхід Sedate Alien для зберігання Мегапункту в спеціальному CFArray, який використовує окремий резервний магазин суміжних виділених Мегаточок (хоча жоден приклад не ілюструє такий підхід). (продовження)
Джастін

але тоді використання NSCFArray проти вектора (або іншого типу stl) призведе до додаткових накладних витрат на динамічну диспетчерику, додаткових викликів функцій, які не вбудовані, тонна безпеки типу і багато шансів для оптимізатора ... стаття просто зосереджена на вставці, читанні, ходінні, видаленні. швидше за все, ви не отримаєте швидше, ніж вирівняний 16-байтний масив Megapoint pt[8];- це опція в c ++ та спеціалізовані контейнери c ++ (наприклад, std::array) - також зверніть увагу, що приклад не додає спеціального вирівнювання (вибрано 16 байтів оскільки це розмір Megapoint). (продовження)
Justin

std::vectorдодасть до цього незначну кількість накладних витрат і один розподіл (якщо ви знаєте розмір, який вам знадобиться) ... але це ближче до металу, ніж потрібно в 99,9% випадків. як правило, ви просто використовуєте вектор, якщо розмір не є фіксованим або має розумний максимум.
Джастін

0

Замість того, щоб намагатися розмістити структуру c у NSArray, ви можете помістити їх у NSData або NSMutableData як масив змінних структур. Щоб отримати до них доступ, ви б зробили це

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 як обгортки може містити лише копію структур, а не оригінальні структури, якщо це для вас має значення.


-3

Для зберігання інформації можна використовувати класи NSObject, крім C-структур. І ви можете легко зберегти цей об'єкт NSO в NSArray.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.