NSArray'e bir c-struct yerleştirmenin en iyi yolu nedir?


89

C yapılarını depolamanın genel yolu nedir NSArray? Avantajlar, dezavantajlar, bellek kullanımı?

Özellikle, justin ve aşağıdaki yayın balığı tarafından yetiştirilen valueWithBytesve arasındaki fark nedir valueWithPointer?

İşte Apple'ın valueWithBytes:objCType:gelecekteki okuyucular için tartışmasına bir bağlantı ...

Bazı yanal düşünme ve daha performans bakarak için Evgen kullanarak konusu gündeme getiriliyor STL::vectoriçinde C ++ .

(Bu ilginç bir sorunu ortaya çıkarıyor: hızlı bir c kütüphanesi var mı, farklı olmayan STL::vectorama çok daha hafif, minimum "dizilerin düzenli işlenmesine" izin veren ...?)

Yani asıl soru ...

Örneğin:

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

Öyleyse: kişinin kendi yapısını böyle bir şekilde saklamanın normal, en iyi, deyimsel yolu NSArraynedir ve bu deyimdeki belleği nasıl ele alırsınız?

Lütfen özellikle yapıları depolamak için her zamanki deyimi aradığımı unutmayın. Elbette, yeni bir küçük sınıf oluşturarak bu sorunu önleyebilirsiniz. Bununla birlikte, yapıları bir diziye koymak için her zamanki deyimin nasıl olduğunu bilmek istiyorum, teşekkürler.

BTW işte NSData yaklaşımı, belki de nedir? en iyisi değil ...

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 bir referans noktası olarak ve Jarret Hardie sayesinde, işte burada nasıl saklanacağı CGPointsve benzerleri NSArray:

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

( bkz.CGPoint nesnelerini bir NSArray'e kolay yoldan nasıl ekleyebilirim? )


onu NSData'ya dönüştürmek için kodunuz iyi olmalıdır .. ve bellek sızıntısı olmamalıdır .... ancak, standart bir C ++ yapı dizisi Megapoint p [3] kullanılabilir;
Swapnil Luktuke

Soru iki günlük olana kadar ödül ekleyemezsiniz.
Matthew Frederick

1
valueWithCGPoint, OSX için mevcut değil. UIKit'in bir parçası
lppier

@Ippier valueWithPoint, OS X'te kullanılabilir
Schpaencoder

Yanıtlar:


160

NSValue yalnızca CoreGraphics yapılarını desteklemez - kendi başınıza da kullanabilirsiniz. Sınıf muhtemelen NSDatabasit veri yapıları için olduğundan daha hafif olduğu için bunu yapmanızı tavsiye ederim .

Aşağıdaki gibi bir ifade kullanın:

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

Ve değeri geri almak için:

Megapoint p;
[value getValue:&p];

4
@Joe Blow @Catfish_Man Aslında yapıyı kopyalar p, ona bir işaretçi değil. @encodeYönerge yapısı ne kadar büyük ilgili gerekli tüm bilgiler verilmektedir. Serbest bıraktığınızda NSValue(veya dizi bıraktığında), yapının kopyası yok edilir. Bu getValue:arada kullandıysanız , iyisiniz. "Sayı ve Değer Programlama Konuları" nın "Değerleri Kullanma" bölümüne bakın: developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Justin Spahr-Summers

1
O hariç Çoğunlukla doğru üfleyin @Joe olmaz zamanında değişime muktedir. Her zaman tam olarak bilinmesi gereken bir C tipi belirtiyorsunuz. Daha verilerine başvurarak "büyük" alabilir, o zaman muhtemelen bir işaretçi ile, uygulamak ve @encodebu işaretleyici ile yapıyı tarif ederim ama değil tam tarif sivri-gerçekten de değişebilir veri.
Justin Spahr-Summers

1
Will NSValueo ayırmanın otomatik yapının hafızasını boşaltmak? Belgeler bu konuda biraz belirsiz.
devios1

1
Tamamen açık olmak gerekirse NSValue, kendi içine kopyaladığı verilerin sahibi ve onu serbest bırakmak konusunda endişelenmeme gerek yok (ARC altında)?
devios1

1
@devios Doğru. NSValuegerçekten herhangi bir “bellek yönetimi” yapmaz - bunu sadece dahili olarak yapı değerinin bir kopyasına sahip olarak düşünebilirsiniz. Örneğin, yapı iç içe geçmiş işaretçiler içeriyorsa, NSValuebunları serbest bırakmayı veya kopyalamayı veya bunlarla hiçbir şey yapmamayı bilemez - bu onları el değmeden bırakır ve adresi olduğu gibi kopyalar.
Justin Spahr-Summers

7

NSValueRotaya sadık kalmanızı öneririm , ancak structNSArray'ınızda (ve Cocoa'daki diğer koleksiyon nesnelerinde) düz 'eski veri türlerini gerçekten depolamak istiyorsanız, bunu dolaylı da olsa Core Foundation ve ücretsiz köprüleme kullanarak yapabilirsiniz. .

CFArrayRef(ve onun değiştirilebilir karşılığı CFMutableArrayRef), geliştiriciye bir dizi nesnesi oluştururken daha fazla esneklik sağlar. Atanan başlatıcının dördüncü argümanına bakın:

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

Bu, CFArrayRefnesnenin Core Foundation'ın bellek yönetimi yordamlarını kullanmasını istemenize olanak tanır , hiçbiri veya kendi bellek yönetimi yordamlarınızı bile .

Zorunlu örnek:

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

Yukarıdaki örnek, bellek yönetimi ile ilgili sorunları tamamen göz ardı etmektedir. Yapı myStructyığın üzerinde yaratılır ve dolayısıyla işlev sona erdiğinde yok edilir - dizi artık orada olmayan bir nesneye bir işaretçi içerecektir. Kendi bellek yönetimi rutinlerinizi kullanarak bu sorunu çözebilirsiniz - bu nedenle bu seçenek size sağlanmıştır - ancak daha sonra referans sayma, bellek ayırma, ayırma vb. Gibi zor işleri yapmanız gerekir.

Bu çözümü tavsiye etmem, ancak başkalarının ilgisini çekmesi durumunda burada saklayacağım. :-)


Yapınızı yığın üzerinde tahsis edildiği gibi kullanmak (yığın yerine) burada gösterilmektedir:

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

Değişken diziler (en azından bu durumda) boş bir durumda oluşturulur ve bu nedenle içinde depolanacak değerlere bir göstericiye ihtiyaç duymaz. Bu, içeriklerin oluşturma sırasında "dondurulduğu" ve dolayısıyla değerlerin başlatma rutinine geçirilmesi gereken değişmez diziden farklıdır.
Sedate Alien

@Joe Blow: Bu, hafıza yönetimi ile ilgili yaptığın mükemmel bir nokta. Kafanızın karışması konusunda haklısınız: Yukarıda yayınladığım kod örneği, işlev yığınının üzerine ne zaman yazıldığına bağlı olarak gizemli çökmelere neden olabilir. Çözümümün nasıl kullanılabileceği konusunda ayrıntılara girmeye başladım, ancak Objective-C'nin kendi referans sayımını yeniden uyguladığımı fark ettim. Kompakt kod için özür dilerim - bu bir yetenek meselesi değil, tembellik meselesi. Başkalarının okuyamayacağı kod yazmanın anlamı yok. :)
Sedate Alien

Eğer "sızdırmaktan" mutlu olsaydınız (daha iyi bir kelime istemek için), structkesinlikle bir kez tahsis edebilir ve gelecekte serbest bırakamazsınız. Düzenlenmiş cevabıma bunun bir örneğini ekledim. Ayrıca, myStructbir göstericiden öbek üzerinde tahsis edilen bir yapıya farklı olarak, yığına tahsis edilmiş bir yapı olduğu için , bir yazım hatası değildi .
Sedate Alien

4

C struct eklemenin benzer bir yöntemi, işaretçiyi depolamak ve bu şekilde işaretçinin referansını kaldırmaktır;

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

Eğer kendinizi inek gibi hissediyorsanız veya gerçekten oluşturacak çok sayıda sınıfınız varsa: dinamik olarak bir objc sınıfı (ref:) oluşturmak bazen yararlıdır class_addIvar. bu şekilde, rastgele türlerden rastgele nesne sınıfları oluşturabilirsiniz. alanı alana göre belirtebilir veya yapının bilgilerini iletebilirsiniz (ancak bu pratik olarak NSData'yı kopyalar). bazen yararlıdır, ancak muhtemelen çoğu okuyucu için daha 'eğlenceli bir gerçek'.

Bunu buraya nasıl uygularım?

class_addIvar'ı çağırabilir ve yeni bir sınıfa bir Megapoint örnek değişkeni ekleyebilir veya çalışma zamanında Megapoint sınıfının bir objc varyantını sentezleyebilirsiniz (örneğin, Megapoint'in her alanı için bir örnek değişkeni).

ilki, derlenen objc sınıfına eşdeğerdir:

@interface MONMegapoint { Megapoint megapoint; } @end

ikincisi, derlenen objc sınıfına eşdeğerdir:

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

ivarları ekledikten sonra yöntemler ekleyebilir / sentezleyebilirsiniz.

alıcı uçta saklanan değerleri okumak için, sentezlenmiş yöntemlerinizi kullanın object_getInstanceVariableveya valueForKey:(bu, bu skaler örnek değişkenlerini genellikle NSNumber veya NSValue gösterimlerine dönüştürür).

btw: Aldığınız tüm cevaplar yararlı, bazıları bağlama / senaryoya bağlı olarak daha iyi / daha kötü / geçersiz. Bellek, hız, bakım kolaylığı, aktarım veya arşivleme kolaylığı vb. ile ilgili özel ihtiyaçlar, belirli bir durum için hangisinin en iyi olduğunu belirleyecektir ... ancak her açıdan ideal olan 'mükemmel' bir çözüm yoktur. 'NSArray'e bir c-struct yerleştirmenin en iyi yolu' yoktur, sadece ' belirli bir senaryo, durum veya gereksinimler kümesi için bir NSArray'e c-struct yerleştirmenin en iyi yolu ' yoktur - belirtmek için.

ayrıca NSArray, işaretçi boyutlu (veya daha küçük) türler için genel olarak yeniden kullanılabilir bir dizi arabirimidir, ancak birçok nedenden ötürü c-yapıları için daha uygun olan başka kaplar da vardır (std :: vektör, c-yapıları için tipik bir seçimdir).


insanların geçmişleri de devreye giriyor ... bu yapıyı nasıl kullanmanız gerektiği çoğu zaman bazı olasılıkları ortadan kaldıracaktır. 4 kayan nokta oldukça kusursuzdur, ancak yapı düzenleri mimariye / derleyiciye göre bitişik bir bellek temsili (örneğin, NSData) kullanmak ve çalışmasını beklemek için çok fazla değişiklik gösterir. zavallı adamın objc serileştiricisi muhtemelen en yavaş yürütme süresine sahiptir, ancak Megapoint'i herhangi bir OS X veya iOS cihazında kaydetmeniz / açmanız / iletmeniz gerektiğinde en uyumlu olanıdır. Tecrübelerime göre en yaygın yol, yapıyı basitçe bir objc sınıfına koymaktır. Sadece (cont) için tüm bunların içine gidiyoruz
justin

(devamı) Tüm bu zorlukları yalnızca yeni bir koleksiyon türünü öğrenmekten kaçınmak için yaşıyorsanız - o zaman yeni koleksiyon türünü öğrenmelisiniz =) std::vector(örneğin) C / C ++ türlerini, yapılarını ve sınıflarını tutmak için olduğundan daha uygundur. NSArray. NSValue, NSData veya NSDictionary türlerinden bir NSArray kullanarak, çok sayıda ayırma ve çalışma zamanı ek yükü eklerken çok fazla tür güvenliği kaybedersiniz. Eğer C'ye bağlı kalmak istiyorsanız, o zaman genellikle yığın üzerinde malloc ve / veya diziler kullanırlar ... ama std::vectorçoğu komplikasyonu sizden gizler.
justin

aslında, bahsettiğiniz gibi dizi manipülasyonu / yinelemesi istiyorsanız - stl (c ++ standart kitaplıklarının bir parçası) bunun için harikadır. aralarından seçim yapabileceğiniz daha fazla tür (örneğin, ekleme / çıkarma okuma erişim zamanlarından daha önemliyse) ve kapsayıcıları işlemek için tonlarca mevcut yol var. ayrıca - c ++ 'da boş bellek değildir - kapsayıcılar ve şablon işlevleri tür farkındadır ve derleme sırasında kontrol edilir - NSData / NSValue temsillerinden rastgele bir bayt dizesi çekmekten çok daha güvenlidir. ayrıca sınır denetimi ve çoğunlukla otomatik bellek yönetimi vardır. (devam)
justin

(devam) bunun gibi çok sayıda düşük seviyeli çalışmanız olacağını düşünüyorsanız, şimdi öğrenmelisiniz - ancak öğrenmeniz zaman alacaktır. Tüm bunları objc temsillerine sararak, kaplara ve değerlerine erişmek ve onları yorumlamak için kendinize çok daha fazla standart kod yazarken, çok fazla performans ve tür güvenliği kaybediyorsunuz (Eğer 'Böylece, çok spesifik olmak gerekirse ...' tam olarak ne yapmak istiyorsan).
justin

sadece emrinizde olan başka bir araçtır. objc'yi c ++ ile, c ++ ile objc, c'yi c ++ ile veya diğer birkaç kombinasyondan herhangi birini entegre etmenin zorlukları olabilir. dil özellikleri eklemek ve birden çok dil kullanmak, her durumda küçük bir maliyetle gelir. her yöne gider. örneğin, objc ++ olarak derlenirken derleme süreleri artar. ayrıca bu kaynaklar diğer projelerde bu kadar kolay kullanılmamaktadır. elbette, dil özelliklerini yeniden uygulayabilirsiniz ... ancak bu genellikle en iyi çözüm değildir. c ++ 'ı bir objc projesine entegre etmek sorun değil, aynı projede objc ve c kaynaklarını kullanmak kadar' dağınık '. (devam
justin

3

Bu verileri birden çok abis / mimaride paylaşıyorsanız, fakir adamın objc serileştiricisini kullanmak en iyisidir:

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

... özellikle yapı zamanla (veya hedeflenen platform tarafından) değişebiliyorsa. diğer seçenekler kadar hızlı değildir, ancak bazı koşullarda (önemli veya önemli olmadığını belirtmediğiniz) bozulma olasılığı daha düşüktür.

serileştirilmiş gösterim süreçten çıkmazsa, rastgele yapıların boyutu / sırası / hizalaması değişmemelidir ve daha basit ve daha hızlı seçenekler vardır.

Her iki durumda da, ref-sayılan bir nesneyi zaten ekliyorsunuz (NSData, NSValue ile karşılaştırıldığında), bu nedenle ... Megapoint'i tutan bir objc sınıfı oluşturmak çoğu durumda doğru cevaptır.


@Joe Serileştirme yapan bir şeyi patlatın. referans için: en.wikipedia.org/wiki/Serialization , parashift.com/c++-faq-lite/serialization.html ve ayrıca Apple'ın "Arşivler ve Serileştirmeler Programlama Kılavuzu".
justin

xml dosyasının bir şeyi doğru şekilde temsil ettiğini varsayarsak, o zaman evet - bu, insan tarafından okunabilir bir serileştirmenin yaygın bir biçimidir.
justin

0

C / C ++ türleri için std :: vector veya std :: list kullanmanızı öneririm, çünkü ilk başta NSArray'den daha hızlıdır ve ikinci olarak sizin için yeterli hız olmayacaksa - her zaman kendinizinkini oluşturabilirsiniz STL kaplar için ayırıcılar ve daha hızlı hale getirin. Tüm modern mobil Oyun, Fizik ve Ses motorları, dahili verileri depolamak için STL kapları kullanır. Sırf gerçekten hızlı oldukları için.

Sizin için değilse - adamlardan NSValue hakkında iyi yanıtlar var - bence en çok kabul edilebilir.


STL, kısmen C ++ Standart Kitaplığı'na dahil edilmiş bir kitaplıktır. en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Evgen Bodunov

Bu ilginç bir iddia. STL kaplarının Cocoa kap sınıflarına göre hız avantajı hakkında bir makaleye bağlantınız var mı?
Sedate Alien

Burada std :: vector vs NSCFArray üzerine ilginç bir okuyun: ridiculousfish.com/blog/archives/2005/12/23/array yazınıza örnekte, en büyük kaybı ((tipik) öğe başına bir objc nesne temsilini yaratmak olduğunu örn Megapoint içeren NSValue, NSData veya Objc türü bir ayırma ve ref-sayılan sisteme ekleme gerektirir). Aslında, Sedate Alien'in bir Megapoint'i, bitişik olarak tahsis edilmiş Megapoints'in ayrı bir destek deposunu kullanan özel bir CFArray'de depolamak için yaklaşımını kullanarak (her iki örnek de bu yaklaşımı göstermese de) bundan kaçınabilirsiniz. (devam)
justin

ancak daha sonra vektör (veya diğer stl türü) karşısında NSCFArray kullanmak, dinamik gönderme için ek yük, satır içi olmayan ek işlev çağrıları, bir ton tür güvenliği ve optimize edicinin devreye girmesi için birçok şans doğuracaktır ... makale sadece ekleme, okuma, yürüme, silme konularına odaklanır. Muhtemelen 16 baytlık hizalanmış bir c dizisinden daha hızlı olamazsınız Megapoint pt[8];- bu c ++ 'da bir seçenektir ve özel c ++ kapsayıcıları (örn. std::array) - ayrıca örneğin özel hizalamayı eklemediğini unutmayın (16 bayt seçilmiştir çünkü Megapoint boyutundadır). (devam)
justin

std::vectorbuna küçük bir miktar ek yük ve bir tahsis (eğer ihtiyacınız olan boyutu biliyorsanız) ekleyecektir ... ancak bu metale, vakaların% 99,9'undan fazlasının ihtiyaç duyduğundan daha yakın. tipik olarak, boyut sabit olmadığı veya makul bir maksimuma sahip olmadığı sürece yalnızca bir vektör kullanırsınız.
justin

0

NSArray'e c struct koymaya çalışmak yerine, onları bir NSData veya NSMutableData içine bir yapı dizisi olarak yerleştirebilirsiniz. Onlara erişmek için yaparsın

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

veya ayarlamak için

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


0

Yapınız için, yapınızı NSValue örneğine çağırmadan bir öznitelik ekleyebilir objc_boxableve @()sözdizimini kullanabilirsiniz 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

Bir Obj C nesnesi, bazı eklenmiş öğeler içeren bir C yapısıdır. Yani sadece özel bir sınıf oluşturun ve NSArray'in gerektirdiği C yapısı türüne sahip olacaksınız. Bir NSObject'in kendi C yapısı içinde içerdiği fazladan ufaklığa sahip olmayan herhangi bir C yapısı, bir NSArray tarafından sindirilemez.

NSData'yı bir sarmalayıcı olarak kullanmak, sizin için bir fark yaratıyorsa orijinal yapıların değil, yalnızca yapıların bir kopyasını depoluyor olabilir.


-3

Bilgileri depolamak için C-Structures dışındaki NSObject sınıflarını kullanabilirsiniz. Ve bu NSObject'i kolayca NSArray'de saklayabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.