NSArrayにc-structを配置する最良の方法は何ですか?


88

c構造体をに保存する通常の方法は何NSArrayですか?メリット、デメリット、メモリ処理?

注目すべきは、違いは何だvalueWithBytesとはvalueWithPointer -ジャスティンと以下のナマズが提起しました。

これvalueWithBytes:objCType:、将来の読者のためのAppleの議論へのリンクです...

いくつかの水平思考、よりパフォーマンスで見ているために、Evgenは、使用しての問題提起しているSTL::vector中でC ++を

(これは興味深い問題を引き起こします:STL::vector最小限の "配列のきちんとした処理"を可能にする高速なCライブラリがありますか?

だから元の質問...

例えば:

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];

ちなみに、参考としてJarret Hardieに感謝します。CGPointsこれは、に保存して類似する方法NSArrayです。

NSArray *points = [NSArray arrayWithObjects:
        [NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
        [NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
        nil];

CGPointオブジェクトをNSArrayに簡単に追加する方法を参照してください


それをNSDataに変換するためのコードは問題なく、メモリリークは発生しません。しかし、構造体Megapoint p [3]の標準C ++配列を使用することもできます。
Swapnil Luktuke、2010

質問が2日経過するまで、賞金を追加することはできません。
マシューフレデリック

1
ただし、valueWithCGPointはOSXでは使用できません。UIKitの一部
lppier

@Ippier valueWithPointはOS Xで利用可能
Schpaencoder 2013

回答:


158

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/...
ジャスティンSpahr-サマーズを

1
@Joe Blow 実行時に変更できないことを除いて、ほとんど正しい。Cタイプを指定しています。これは常に完全に認識されている必要があります。より多くのデータを参照することで「より大きく」なる可能性がある場合、おそらくポインターでそれを実装し、そのポインターで@encode構造を記述しますが、実際に変更される可能性があるポイントされたデータを完全には記述しません
Justin Spahr-Summers

1
ウィルNSValueそれが割り当て解除されたときに自動的に構造体のメモリを解放?これについてのドキュメントは少し不明確です。
devios1

1
だから完全に明確にNSValueするために、それはそれ自体にコピーするデータを所有し、私はそれを解放することを心配する必要はありません(ARCの下で)?
devios1

1
@devios正解です。NSValueそれ自体は、実際には「メモリ管理」を何も実行しません。内部的に構造値のコピーを持っていると考えることができます。たとえば、構造にネストされたポインターが含まれている場合、NSValueそれらを解放するか、コピーするか、何かを実行することがわかりません。アドレスをそのままコピーし、そのままにします。
Justin Spahr-Summers 2015

7

NSValueルートに固執することをお勧めしますstructが、NSArray(およびCocoaの他のコレクションオブジェクト)にプレーン 'ol データ型を保存したい場合は、間接的にではありますが、Core Foundationとフリーダイヤルブリッジを使用して保存できます。。

CFArrayRef(およびその変更可能な対応物CFMutableArrayRef)は、配列オブジェクトを作成するときに開発者に柔軟性を提供します。指定されたイニシャライザの4番目の引数を参照してください。

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 2010

(より良い言葉が欲しいので)を「リーク」してよかった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インスタンス変数を新しいクラスに追加するか、実行時にMegapointクラスのobjcバリアントを合成できます(たとえば、Megapointの各フィールドのインスタンス変数)。

前者は、コンパイルされたobjcクラスと同等です。

@interface MONMegapoint { Megapoint megapoint; } @end

後者は、コンパイルされたobjcクラスと同等です。

@interface MONMegapoint { float w,x,y,z; } @end

ivarを追加したら、メソッドを追加/合成できます。

受信側で格納された値を読み取るには、合成されたメソッドobject_getInstanceVariable、またはを使用しますvalueForKey:(これらのスカラーインスタンス変数がNSNumberまたはNSValue表現に変換されることがよくあります)。

ところで:あなたが受け取ったすべての答えは役に立ちます、いくつかは状況/シナリオに応じてより良い/悪い/無効です。メモリ、速度、保守のしやすさ、転送またはアーカイブのしやすさなどの特定のニーズによって、特定のケースに最適なものが決定されます...しかし、あらゆる点で理想的な「完璧な」ソリューションはありません。「NSArrayにc-structを配置するための最良の方法」はありません。「特定のシナリオ、ケース、または要件セットについて、NSArrayにc-structを配置するための最良の方法」があります。指定します。

さらに、NSArrayは、一般的にポインタサイズ(またはより小さい)型の再利用可能な配列インターフェイスですが、さまざまな理由でc-structに適したコンテナがあります(std :: vectorがc-structの一般的な選択肢です)。


人々のバックグラウンドも影響します...その構造体をどのように使用する必要があるかによって、多くの場合、いくつかの可能性が排除されます。4 floatは非常に簡単ですが、構造体のレイアウトはアーキテクチャ/コンパイラによって異なり、連続したメモリ表現(NSDataなど)を使用して機能しない場合があります。貧乏人のobjcシリアライザーは実行時間が最も遅い可能性がありますが、任意のOS XまたはiOSデバイスでMegapointを保存/オープン/送信する必要がある場合に最も互換性があります。最も一般的な方法は、私の経験では、構造体を単にobjcクラスに配置することです。これらすべてを(続き)に進む場合
2010

(続き)新しいコレクションタイプの学習を避けるためだけにこの面倒を経験している場合は、新しいコレクションタイプを学習する必要があります=)std::vector(たとえば)よりもC / C ++タイプ、構造体、およびクラスの保持に適していますNSArray。NSValue、NSData、またはNSDictionary型のNSArrayを使用すると、大量の割り当てとランタイムオーバーヘッドを追加する際に、多くの型安全性が失われます。Cを使い続けたい場合は、通常、スタックでmallocまたは配列、あるいはその両方を使用しますがstd::vector、複雑さのほとんどを隠します。
2010

実際、前述のように配列の操作/反復が必要な場合は、stl(c ++標準ライブラリの一部)が最適です。選択できるタイプが多く(たとえば、読み取り/アクセス時間よりも挿入/削除の方が重要な場合)、コンテナを操作する既存の方法がたくさんあります。また、それはc ++の裸のメモリではありません-コンテナとテンプレート関数はタイプを認識し、コンパイル時にチェックされます-NSData / NSValue表現から任意のバイト文字列を引き出すよりもはるかに安全です。また、境界チェックとメモリの自動管理も含まれます。(続き)
2010

(続き)このような低レベルの作業がたくさんあると予想する場合は、今すぐ学習する必要がありますが、学習には時間がかかります。これをすべてobjc表現でラップすると、多くのパフォーマンスと型の安全性が失われますが、コンテナーとその値にアクセスして解釈するためのはるかに多くのボイラープレートコードを作成することになります(「したがって、非常に具体的に...」まさにあなたがしたいこと)。
2010

それはあなたが自由に使えるもう一つのツールです。objcをc ++に、c ++をobjcに、cをc ++に、または他のいくつかの組み合わせのいずれかを統合すると、複雑になる可能性があります。言語機能を追加して複数の言語を使用することは、いずれにせよ、わずかなコストで済みます。それはすべての方法になります。たとえば、objc ++としてコンパイルすると、ビルド時間が長くなります。同様に、これらのソースは他のプロジェクトで簡単に再利用できません。確かに、言​​語機能を再実装することはできますが、それが最良の解決策であるとは限りません。c ++をobjcプロジェクトに統合するのは問題ありません。同じプロジェクトでobjcとcのソースを使用するのと同じくらい「厄介」です。(cont
justin

3

複数のabis / architecturesでこのデータを共有する場合は、貧乏人のobjcシリアライザーを使用するのが最善です。

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クラスを作成することが多くの場合正しい答えです。


@Joe Blowシリアル化を実行するもの。参考のために:en.wikipedia.org/wiki/Serialization、parashift.com/c ++ - faq-lite/serialization.html、およびAppleの「アーカイブおよびシリアライゼーションプログラミングガイド」。
2010

xmlファイルが適切に何かを表すと仮定すると、はい-それは人間が読めるシリアル化の一般的な形式の1つです。
2010

0

C / C ++タイプにはstd :: vectorまたはstd :: listを使用することをお勧めします。これは、最初はNSArrayよりも高速であり、次に十分な速度が得られない場合は、常に自分で作成できるためです。 STLコンテナーのアロケーターを使用して、さらに高速化します。最新のすべてのモバイルゲーム、物理、オーディオエンジンは、STLコンテナを使用して内部データを保存しています。彼らが本当に速いからといって。

それがあなたのためではない場合-NSValueについてはみんなから良い答えがあります-それは最も受け入れられると思います。


STLは、C ++標準ライブラリに部分的に含まれているライブラリです。en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Bodunov

それは興味深い主張です。STLコンテナーとCocoaコンテナークラスの速度の利点についての記事へのリンクはありますか?
Sedate Alien

これは、投稿の例のNSCFArrayとstd :: vector:ridiculousfish.com/blog/archives/2005/12/23/arrayの興味深い記事です。最大の損失は、(通常)要素ごとにobjcオブジェクト表現を作成することです(たとえば、NSValue、NSData、またはMegapointを含むObjcタイプには、割り当てと参照カウントシステムへの挿入が必要です)。隣接して割り当てられたメガポイントの個別のバッキングストアを使用する特別なCFArrayにメガポイントを格納するSedate Alienのアプローチを使用することで実際にそれを回避できます(どちらの例もそのアプローチを示していません)。(続き)
2010

しかし、NSCFArrayとベクトル(または他のstl型)を使用すると、動的ディスパッチ、インライン化されない追加の関数呼び出し、大量の型安全性、およびオプティマイザーが起動する多くの機会が発生します...記事は単に挿入、読み取り、ウォーク、削除に焦点を当てています。可能性はありますが、16バイトで整列されたc配列より速くなることはありませんMegapoint pt[8];-これはc ++のオプションであり、特殊なc ++コンテナー(例:std::array)-また、例では特別な整列が追加されていません(16バイトが選択されています)それはメガポイントのサイズだからです)。(続き)
2010

std::vectorこれにわずかなオーバーヘッドが追加され、1つの割り当て(必要なサイズがわかっている場合)が追加されます...しかし、これは必要なケースの99.9%以上よりも金属に近いです。通常、サイズが固定されていないか、適切な最大値がない限り、ベクトルを使用します。
2010

0

C構造体をNSArrayに配置する代わりに、構造体のC配列として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

構造については、属性 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構造体です。したがって、カスタムクラスを作成するだけで、NSArrayに必要なタイプのC構造体が得られます。NSObjectがそのC構造内に含んでいる余分な欠けがないC構造は、NSArrayで消化できなくなります。

NSDataをラッパーとして使用すると、構造が異なる場合、元の構造ではなく、構造のコピーのみが格納されます。


-3

C-Structures以外のNSObjectクラスを使用して情報を保存できます。そして、そのNSObjectをNSArrayに簡単に格納できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.