Apa cara terbaik untuk menempatkan c-struct di NSArray?

89

Apa cara yang biasa untuk menyimpan c-structure di sebuah NSArray? Keuntungan, kerugian, penanganan memori?

Khususnya, apa perbedaan antara valueWithBytesdan valueWithPointer - dibesarkan oleh justin dan lele di bawah ini.

Berikut tautan ke diskusi Apple valueWithBytes:objCType:untuk pembaca di masa mendatang ...

Untuk beberapa pemikiran lateral dan melihat lebih banyak pada kinerja, Evgen telah mengangkat masalah penggunaan STL::vectordalam C ++ .

(Itu menimbulkan masalah yang menarik: apakah ada pustaka c yang cepat, tidak seperti STL::vectortetapi jauh lebih ringan, yang memungkinkan "penanganan larik yang rapi" minimal ...?)

Jadi pertanyaan aslinya ...

Sebagai contoh:

typedef struct _Megapoint {
    float   w,x,y,z;
} Megapoint;

Jadi: apa cara normal, terbaik, idiomatik untuk menyimpan struktur sendiri seperti itu dalam sebuah NSArray, dan bagaimana Anda menangani memori dalam idiom itu?

Harap dicatat bahwa saya secara khusus mencari idiom biasa untuk menyimpan struct. Tentu saja, seseorang dapat menghindari masalah ini dengan membuat kelas kecil yang baru. Namun saya ingin tahu bagaimana idiom biasa untuk benar-benar meletakkan struct dalam array, terima kasih.

BTW inilah pendekatan NSData yang mungkin? bukan yang terbaik ...

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

BTW sebagai rujukan dan terima kasih kepada Jarret Hardie, berikut cara menyimpan CGPointsdan sejenisnya di NSArray:

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

(lihat Bagaimana cara menambahkan objek CGPoint ke NSArray dengan cara mudah? )

Fattie
sumber
kode Anda untuk mengubahnya menjadi NSData seharusnya baik-baik saja .. dan tanpa kebocoran memori .... namun, orang mungkin juga menggunakan array C ++ standar dari struct Megapoint p [3];
Swapnil Luktuke
Anda tidak dapat menambahkan hadiah sampai pertanyaan berumur dua hari.
Matthew Frederick
1
valueWithCGPoint tidak tersedia untuk OSX. Ini bagian dari UIKit
lppier
@Ippier valueWithPoint tersedia di OS X
Schpaencoder

Jawaban:

160

NSValue tidak hanya mendukung struktur CoreGraphics - Anda juga dapat menggunakannya untuk Anda sendiri. Saya akan merekomendasikan melakukannya, karena bobot kelas mungkin lebih ringan daripada NSDatastruktur data sederhana.

Cukup gunakan ekspresi seperti berikut:

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

Dan untuk mendapatkan kembali nilainya:

Megapoint p;
[value getValue:&p];
Justin Spahr-Summers
sumber
4
@ Joe Blow @Catfish_Man Ini sebenarnya menyalin struktur p, bukan penunjuk ke sana. The @encodedirektif menyediakan semua informasi yang diperlukan tentang seberapa besar struktur. Saat Anda melepaskan NSValue(atau saat array melakukannya), salinan strukturnya akan dimusnahkan. Jika Anda pernah menggunakannya getValue:untuk sementara, Anda baik-baik saja. Lihat bagian "Menggunakan Nilai" dari "Topik Pemrograman Angka dan Nilai": developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Justin Spahr-Summers
1
@ Joe Blow Sebagian besar benar, kecuali bahwa ini tidak akan dapat diubah pada saat runtime. Anda menentukan tipe C, yang harus selalu diketahui sepenuhnya. Jika bisa menjadi "lebih besar" dengan mereferensikan lebih banyak data, maka Anda mungkin akan menerapkannya dengan pointer, dan @encodeakan menjelaskan struktur dengan pointer itu, tetapi tidak sepenuhnya mendeskripsikan data yang diarahkan ke, yang memang bisa berubah.
Justin Spahr-Summers
1
Akan NSValuesecara otomatis mengosongkan memori struct ketika dibatalkan alokasinya? Dokumentasinya sedikit tidak jelas tentang ini.
devios1
1
Jadi hanya untuk memperjelas, NSValuepemilik data yang disalin ke dalamnya dan saya tidak perlu khawatir tentang membebaskannya (di bawah ARC)?
devios1
1
@devi Benar. NSValuetidak benar-benar melakukan “manajemen memori,” per se — Anda dapat menganggapnya hanya sebagai memiliki salinan nilai struktur secara internal. Jika struktur berisi pointer bertingkat, misalnya, NSValuetidak akan tahu untuk membebaskan atau menyalin atau melakukan apa pun dengan itu — itu akan membuat mereka tidak tersentuh, menyalin alamat apa adanya.
Justin Spahr-Summers
7

Saya akan menyarankan Anda tetap menggunakan NSValuerute, tetapi jika Anda benar-benar ingin menyimpan structtipe data biasa di NSArray Anda (dan objek koleksi lainnya di Cocoa), Anda dapat melakukannya - meskipun secara tidak langsung, menggunakan Core Foundation dan penghubung bebas pulsa .

CFArrayRef(dan rekannya yang bisa berubah, CFMutableArrayRef) memberi pengembang lebih banyak fleksibilitas saat membuat objek array. Lihat argumen keempat dari penginisialisasi yang ditunjuk:

CFArrayRef CFArrayCreate (
    CFAllocatorRef allocator,
    const void **values,
    CFIndex numValues,
    const CFArrayCallBacks *callBacks
);

Hal ini memungkinkan Anda untuk meminta CFArrayRefobjek menggunakan rutinitas manajemen memori Core Foundation, tidak ada sama sekali atau bahkan rutinitas manajemen memori Anda sendiri .

Contoh wajib:

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

Contoh di atas sepenuhnya mengabaikan masalah yang terkait dengan manajemen memori. Struktur myStructdibuat di tumpukan dan karenanya dihancurkan ketika fungsi berakhir - array akan berisi penunjuk ke objek yang sudah tidak ada lagi. Anda dapat mengatasinya dengan menggunakan rutinitas manajemen memori Anda sendiri - karena itulah mengapa opsi ini disediakan untuk Anda - tetapi kemudian Anda harus bekerja keras menghitung referensi, mengalokasikan memori, membatalkan alokasi, dan sebagainya.

Saya tidak akan merekomendasikan solusi ini, tetapi akan menyimpannya di sini jika itu menarik bagi orang lain. :-)


Menggunakan struktur Anda seperti yang dialokasikan di heap (sebagai pengganti tumpukan) ditunjukkan di sini:

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];
Alien Tenang
sumber
Array yang dapat berubah (setidaknya dalam kasus ini) dibuat dalam keadaan kosong dan karenanya tidak memerlukan penunjuk ke nilai yang akan disimpan di dalamnya. Ini berbeda dari larik yang tidak dapat diubah di mana isinya "dibekukan" saat pembuatan dan karenanya nilai harus diteruskan ke rutin inisialisasi.
Sedate Alien
@ Joe Pukulan: Itu poin yang sangat baik yang Anda buat tentang manajemen memori. Anda benar akan bingung: contoh kode yang saya posting di atas akan menyebabkan crash misterius, bergantung pada kapan tumpukan fungsi ditimpa. Saya mulai menguraikan tentang bagaimana solusi saya dapat digunakan, tetapi menyadari bahwa saya menerapkan ulang penghitungan referensi Objective-C sendiri. Saya minta maaf untuk kode ringkas ini - ini bukan masalah bakat tetapi kemalasan. Tidak ada gunanya menulis kode yang tidak bisa dibaca orang lain. :)
Sedate Alien
Jika Anda senang untuk "membocorkan" (karena menginginkan kata yang lebih baik) struct, Anda pasti dapat mengalokasikannya sekali dan tidak membebaskannya di masa mendatang. Saya telah menyertakan contoh ini dalam jawaban saya yang diedit. Selain itu, ini bukan kesalahan ketik myStruct, karena ini adalah struktur yang dialokasikan pada tumpukan, berbeda dari penunjuk ke struktur yang dialokasikan di heap.
Sedate Alien
4

Metode serupa untuk menambahkan c struct adalah dengan menyimpan penunjuk dan membatalkan referensi penunjuk seperti itu;

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];
Keynes
sumber
3

jika Anda merasa kutu buku, atau benar-benar memiliki banyak kelas untuk dibuat: terkadang berguna untuk membuat kelas objc secara dinamis (ref:) class_addIvar. dengan cara ini, Anda bisa membuat kelas objc dari jenis arbitrer. Anda dapat menentukan bidang demi bidang, atau hanya meneruskan info dari struct (tapi itu mereplikasi NSData secara praktis). terkadang berguna, tetapi mungkin lebih merupakan 'fakta menyenangkan' bagi kebanyakan pembaca.

Bagaimana saya menerapkan ini di sini?

Anda dapat memanggil class_addIvar dan menambahkan variabel instance Megapoint ke kelas baru, atau Anda dapat mensintesis varian objc dari kelas Megapoint saat runtime (misalnya, variabel instance untuk setiap bidang Megapoint).

yang pertama setara dengan kelas objc yang dikompilasi:

@interface MONMegapoint { Megapoint megapoint; } @end

yang terakhir ini setara dengan kelas objc yang dikompilasi:

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

setelah Anda menambahkan ivars, Anda dapat menambahkan / mensintesis metode.

untuk membaca nilai yang disimpan di sisi penerima, gunakan metode yang disintesis object_getInstanceVariable, atau valueForKey:(yang akan sering mengubah variabel instance skalar ini menjadi representasi NSNumber atau NSValue).

btw: semua jawaban yang Anda terima berguna, beberapa lebih baik / lebih buruk / tidak valid tergantung konteks / skenario. kebutuhan khusus terkait memori, kecepatan, kemudahan pemeliharaan, kemudahan untuk mentransfer atau mengarsipkan, dll. akan menentukan mana yang terbaik untuk kasus tertentu ... tetapi tidak ada solusi 'sempurna' yang ideal dalam segala hal. tidak ada 'cara terbaik untuk menempatkan c-struct di NSArray', hanya 'cara terbaik untuk meletakkan c-struct di NSArray untuk skenario, kasus, atau serangkaian persyaratan tertentu ' - yang Anda akan miliki untuk menentukan.

selanjutnya, NSArray adalah antarmuka array yang umumnya dapat digunakan kembali untuk tipe pointer berukuran (atau lebih kecil), tetapi ada kontainer lain yang lebih cocok untuk c-struct karena berbagai alasan (std :: vector menjadi pilihan tipikal untuk c-struct).

justin
sumber
latar belakang orang-orang ikut bermain juga ... bagaimana Anda perlu menggunakan struct itu akan sering menghilangkan beberapa kemungkinan. 4 float cukup mudah, tetapi tata letak struct bervariasi menurut arsitektur / kompiler terlalu banyak untuk menggunakan representasi memori yang berdekatan (misalnya, NSData) dan mengharapkannya untuk bekerja. Penyambung obyektif pria malang itu kemungkinan memiliki waktu eksekusi paling lambat, tetapi paling kompatibel jika Anda perlu menyimpan / membuka / mengirimkan Megapoint pada perangkat OS X atau iOS apa pun. Cara yang paling umum, menurut pengalaman saya adalah dengan meletakkan struct di kelas objc. jika Anda akan melalui semua ini hanya untuk (cont)
justin
(lanjutan) Jika Anda melalui semua kerumitan ini hanya untuk menghindari mempelajari jenis koleksi baru - maka Anda harus mempelajari jenis koleksi baru =) std::vector(misalnya) lebih cocok untuk menahan jenis C / C ++, struct dan kelas daripada NSArray. Dengan menggunakan jenis NSArray NSValue, NSData, atau NSDictionary, Anda kehilangan banyak keamanan jenis saat menambahkan banyak alokasi dan overhead waktu proses. Jika Anda ingin tetap menggunakan C, maka mereka umumnya akan menggunakan malloc dan / atau array di stack ... tetapi std::vectormenyembunyikan sebagian besar komplikasi dari Anda.
justin
sebenarnya, jika Anda ingin manipulasi / iterasi array seperti yang Anda sebutkan - stl (bagian dari pustaka standar c ++) sangat bagus untuk itu. Anda memiliki lebih banyak tipe untuk dipilih (misalnya, jika menyisipkan / menghapus lebih penting daripada waktu akses baca), dan banyak cara yang ada untuk memanipulasi container. juga - ini bukan memori kosong di c ++ - container dan fungsi template sadar-tipe, dan diperiksa saat kompilasi - jauh lebih aman daripada menarik string byte arbitrer dari representasi NSData / NSValue. mereka juga memiliki pemeriksaan batas dan sebagian besar manajemen memori otomatis. (cont)
justin
(lanjutan) jika Anda berharap akan mendapatkan banyak pekerjaan tingkat rendah seperti ini, maka Anda harus mempelajarinya sekarang - tetapi perlu waktu untuk mempelajarinya. dengan membungkus ini semua dalam representasi objc, Anda kehilangan banyak kinerja dan keamanan tipe, sementara membuat diri Anda menulis lebih banyak kode boilerplate untuk mengakses dan menafsirkan wadah dan nilainya (Jika 'Jadi, untuk menjadi sangat spesifik ...' adalah persis apa yang ingin Anda lakukan).
justin
itu hanyalah alat lain yang Anda inginkan. mungkin ada kerumitan yang mengintegrasikan objc dengan c ++, c ++ dengan objc, c dengan c ++ atau beberapa kombinasi lainnya. menambahkan fitur bahasa dan menggunakan berbagai bahasa memang membutuhkan biaya yang kecil. itu berjalan jauh. misalnya, waktu build naik saat mengompilasi sebagai objc ++. juga, sumber-sumber ini tidak digunakan kembali dalam proyek lain dengan mudah. tentu saja, Anda dapat menerapkan ulang fitur bahasa ... tetapi itu sering kali bukan solusi terbaik. mengintegrasikan c ++ dalam proyek objc baik-baik saja, ini sama 'berantakan' seperti menggunakan sumber objc dan c dalam proyek yang sama. (lanjutan
hanya dalam
3

akan lebih baik menggunakan serializer objc orang miskin jika Anda berbagi data ini di beberapa abis / arsitektur:

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

... terutama jika strukturnya dapat berubah seiring waktu (atau berdasarkan platform yang ditargetkan). tidak secepat opsi lain, tetapi cenderung tidak rusak dalam beberapa kondisi (yang belum Anda tetapkan sebagai penting atau tidak).

jika representasi berseri tidak keluar dari proses, maka ukuran / urutan / penyelarasan struct arbitrer tidak boleh berubah, dan ada opsi yang lebih sederhana dan lebih cepat.

di kedua acara, Anda sudah menambahkan objek ref-count (dibandingkan dengan NSData, NSValue) jadi ... membuat kelas objc yang menampung Megapoint adalah jawaban yang tepat dalam banyak kasus.

justin
sumber
@ Joe Pukulan sesuatu yang melakukan serialisasi. untuk referensi: en.wikipedia.org/wiki/Serialization , parashift.com/c++-faq-lite/serialization.html , serta "Panduan Pemrograman Arsip dan Serialisasi" Apple.
justin
dengan asumsi file xml mewakili sesuatu dengan benar, maka ya - itu adalah salah satu bentuk umum dari serialisasi yang dapat dibaca manusia.
justin
0

Saya menyarankan Anda untuk menggunakan std :: vector atau std :: list untuk tipe C / C ++, karena pada awalnya hanya lebih cepat daripada NSArray, dan yang kedua jika tidak ada cukup kecepatan untuk Anda - Anda selalu dapat membuatnya sendiri pengalokasi untuk kontainer STL dan membuatnya lebih cepat. Semua mesin Game, Fisika, dan Audio seluler modern menggunakan wadah STL untuk menyimpan data internal. Hanya karena mereka sangat cepat.

Jika bukan untuk Anda - ada jawaban bagus dari orang-orang tentang NSValue - menurut saya itu paling bisa diterima.

Evgen Bodunov
sumber
STL adalah pustaka yang sebagian disertakan ke dalam C ++ Standard Library. en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Evgen Bodunov
Itu klaim yang menarik. Apakah Anda memiliki link ke artikel tentang keunggulan kecepatan penampung STL vs. kelas penampung Kakao?
Sedate Alien
inilah bacaan menarik tentang NSCFArray vs std :: vector: ridiculousfish.com/blog/archives/2005/12/23/array dalam contoh di posting Anda, kerugian terbesar adalah (biasanya) membuat representasi objek object per elemen (mis. , NSValue, NSData, atau jenis Objc yang berisi Megapoint memerlukan alokasi dan penyisipan ke dalam sistem yang dihitung ulang). Anda sebenarnya dapat menghindarinya dengan menggunakan pendekatan Sedate Alien untuk menyimpan Megapoint dalam CFArray khusus yang menggunakan penyimpanan pendukung terpisah dari Megapoint yang dialokasikan secara berdekatan (meskipun tidak ada contoh yang menggambarkan pendekatan itu). (cont)
justin
tetapi kemudian menggunakan NSCFArray vs vektor (atau tipe stl lainnya) akan menimbulkan overhead tambahan untuk pengiriman dinamis, panggilan fungsi tambahan yang tidak sebaris, banyak jenis keamanan, dan banyak peluang bagi pengoptimal untuk memulai ... itu Artikel hanya berfokus pada menyisipkan, membaca, berjalan, menghapus. kemungkinan besar, Anda tidak akan mendapatkan lebih cepat dari c-array 16 byte Megapoint pt[8];- ini adalah opsi di c ++, dan wadah khusus c ++ (misalnya, std::array) - juga perhatikan bahwa contoh tidak menambahkan penyelarasan khusus (16 byte dipilih karena ukurannya sebesar Megapoint). (cont)
justin
std::vectorakan menambah sejumlah kecil biaya tambahan untuk ini, dan satu alokasi (jika Anda tahu ukuran yang Anda perlukan) ... tetapi ini lebih mendekati logam daripada yang dibutuhkan lebih dari 99,9% kasing. biasanya, Anda hanya akan menggunakan vektor kecuali ukurannya tetap atau memiliki maksimum yang wajar.
justin
0

Alih-alih mencoba menempatkan c struct dalam NSArray, Anda dapat menempatkannya di NSData atau NSMutableData sebagai larik ac dari struct. Untuk mengaksesnya, Anda harus melakukannya

const struct MyStruct    * theStruct = (const struct MyStruct*)[myData bytes];
int                      value = theStruct[2].integerNumber;

atau untuk mengaturnya

struct MyStruct    * theStruct = (struct MyStruct*)[myData mutableBytes];
theStruct[2].integerNumber = 10;
Nathan Day
sumber
0

Saat menggunakan NSValue berfungsi dengan baik untuk menyimpan struct sebagai objek Obj-C, Anda tidak dapat menyandikan NSValue yang berisi struct dengan NSArchiver / NSKeyedArchiver. Sebagai gantinya, Anda harus menyandikan anggota struct individu ...

Lihat Panduan Pemrograman Arsip dan Serialisasi Apple> Struktur dan Bidang Bit

DarkMatter
sumber
0

Untuk struktur Anda, Anda dapat menambahkan atribut objc_boxable dan menggunakan @()sintaksis untuk meletakkan struktur Anda ke dalam instance NSValue tanpa memanggil 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)];
Andrew Romanov
sumber
-2

Objek Obj C hanyalah sebuah struct C dengan beberapa elemen tambahan. Jadi buat saja kelas khusus dan Anda akan memiliki tipe struct C yang dibutuhkan NSArray. Struct C apa pun yang tidak memiliki cruft ekstra yang dimasukkan oleh NSObject di dalam struct C-nya akan tidak dapat dicerna oleh NSArray.

Menggunakan NSData sebagai pembungkus mungkin hanya menyimpan salinan struct dan bukan struct asli, jika itu membuat perbedaan bagi Anda.

hotpaw2
sumber
-3

Anda dapat menggunakan kelas NSObject selain C-Structures untuk menyimpan informasi. Dan Anda dapat dengan mudah menyimpan NSObject itu ke NSArray.

Satya
sumber