Apa cara yang biasa untuk menyimpan c-structure di sebuah NSArray
? Keuntungan, kerugian, penanganan memori?
Khususnya, apa perbedaan antara valueWithBytes
dan 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::vector
dalam C ++ .
(Itu menimbulkan masalah yang menarik: apakah ada pustaka c yang cepat, tidak seperti STL::vector
tetapi 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 CGPoints
dan 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? )
Jawaban:
NSValue tidak hanya mendukung struktur CoreGraphics - Anda juga dapat menggunakannya untuk Anda sendiri. Saya akan merekomendasikan melakukannya, karena bobot kelas mungkin lebih ringan daripada
NSData
struktur data sederhana.Cukup gunakan ekspresi seperti berikut:
[NSValue valueWithBytes:&p objCType:@encode(Megapoint)];
Dan untuk mendapatkan kembali nilainya:
Megapoint p; [value getValue:&p];
sumber
p
, bukan penunjuk ke sana. The@encode
direktif menyediakan semua informasi yang diperlukan tentang seberapa besar struktur. Saat Anda melepaskanNSValue
(atau saat array melakukannya), salinan strukturnya akan dimusnahkan. Jika Anda pernah menggunakannyagetValue:
untuk sementara, Anda baik-baik saja. Lihat bagian "Menggunakan Nilai" dari "Topik Pemrograman Angka dan Nilai": developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…@encode
akan menjelaskan struktur dengan pointer itu, tetapi tidak sepenuhnya mendeskripsikan data yang diarahkan ke, yang memang bisa berubah.NSValue
secara otomatis mengosongkan memori struct ketika dibatalkan alokasinya? Dokumentasinya sedikit tidak jelas tentang ini.NSValue
pemilik data yang disalin ke dalamnya dan saya tidak perlu khawatir tentang membebaskannya (di bawah ARC)?NSValue
tidak benar-benar melakukan “manajemen memori,” per se — Anda dapat menganggapnya hanya sebagai memiliki salinan nilai struktur secara internal. Jika struktur berisi pointer bertingkat, misalnya,NSValue
tidak akan tahu untuk membebaskan atau menyalin atau melakukan apa pun dengan itu — itu akan membuat mereka tidak tersentuh, menyalin alamat apa adanya.Saya akan menyarankan Anda tetap menggunakan
NSValue
rute, tetapi jika Anda benar-benar ingin menyimpanstruct
tipe 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
CFArrayRef
objek 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
myStruct
dibuat 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];
sumber
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 ketikmyStruct
, karena ini adalah struktur yang dialokasikan pada tumpukan, berbeda dari penunjuk ke struktur yang dialokasikan di heap.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];
sumber
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.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
, atauvalueForKey:
(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).
sumber
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 ... tetapistd::vector
menyembunyikan sebagian besar komplikasi dari Anda.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.
sumber
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.
sumber
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)std::vector
akan 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.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;
sumber
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
sumber
Untuk struktur Anda, Anda dapat menambahkan atribut
objc_boxable
dan menggunakan@()
sintaksis untuk meletakkan struktur Anda ke dalam instance NSValue tanpa memanggilvalueWithBytes: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)];
sumber
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.
sumber
Anda dapat menggunakan kelas NSObject selain C-Structures untuk menyimpan informasi. Dan Anda dapat dengan mudah menyimpan NSObject itu ke NSArray.
sumber