Qual é a melhor maneira de colocar uma c-struct em um NSArray?

88

Qual é a maneira usual de armazenar estruturas c em um NSArray? Vantagens, desvantagens, manipulação de memória?

Notavelmente, qual é a diferença entre valueWithBytese valueWithPointer - criado por justin e bagre abaixo.

Aqui está um link para a discussão da Apple sobre valueWithBytes:objCType:para futuros leitores ...

Para pensar lateralmente e olhar mais para o desempenho, Evgen levantou a questão do uso STL::vectorem C ++ .

(Isso levanta uma questão interessante: existe uma biblioteca c rápida, não diferente, STL::vectormas muito mais leve, que permite o mínimo "manuseio organizado de matrizes" ...?)

Então, a pergunta original ...

Por exemplo:

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

Então: qual é a maneira normal, melhor e idiomática de armazenar sua própria estrutura como essa em um NSArray, e como você lida com a memória nesse idioma?

Observe que estou procurando especificamente o idioma usual para armazenar structs. Claro, pode-se evitar o problema fazendo uma pequena classe nova. No entanto, quero saber como funciona o idioma usual para realmente colocar structs em um array, obrigado.

BTW, aqui está a abordagem NSData que talvez seja? não é o melhor ...

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 como um ponto de referência e graças a Jarret Hardie, veja como armazenar CGPointse semelhantes em NSArray:

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

(veja Como posso adicionar objetos CGPoint a um NSArray da maneira mais fácil? )

Fattie
fonte
seu código para convertê-lo em NSData deve estar bem ... e sem vazamentos de memória ... no entanto, pode-se também usar um array C ++ padrão de estruturas Megapoint p [3];
Swapnil Luktuke
Você não pode adicionar uma recompensa até que a pergunta tenha dois dias.
Matthew Frederick
1
valueWithCGPoint não está disponível para OSX embora. Faz parte do UIKit
lppier
@Ippier valueWithPoint está disponível no OS X
Schpaencoder

Respostas:

158

NSValue não suporta apenas estruturas CoreGraphics - você também pode usá-lo para si. Eu recomendaria fazer isso, pois a classe provavelmente é mais leve do que NSDatapara estruturas de dados simples.

Basta usar uma expressão como a seguinte:

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

E para obter o valor de volta:

Megapoint p;
[value getValue:&p];
Justin Spahr-Summers
fonte
4
@Joe Blow @Catfish_Man Ele realmente copia a estrutura p, não um ponteiro para ela. A @encodediretiva fornece todas as informações necessárias sobre o tamanho da estrutura. Quando você libera o NSValue(ou quando o array o faz), sua cópia da estrutura é destruída. Se você já usou getValue:, você está bem. Consulte a seção "Usando valores" de "Tópicos de programação de números e valores": developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Justin Spahr-Summers
1
@Joe Blow Principalmente correto, exceto que não poderia ser alterado em tempo de execução. Você está especificando um tipo C, que sempre deve ser totalmente conhecido. Se pudesse ficar "maior" referenciando mais dados, então você provavelmente implementaria isso com um ponteiro, e @encodedescreveria a estrutura com esse ponteiro, mas não descreveria totalmente os dados apontados, que poderiam de fato mudar.
Justin Spahr-Summers
1
Libertará NSValueautomaticamente a memória da estrutura quando ela for desalocada? A documentação é um pouco confusa sobre isso.
devios1
1
Então, apenas para ficar completamente claro, o NSValuepossui os dados que copia para si mesmo e não tenho que me preocupar em liberá-lo (no ARC)?
devios1
1
@devios Correto. NSValuenão faz nenhum “gerenciamento de memória” per se - você pode pensar nisso como apenas ter uma cópia do valor da estrutura internamente. Se a estrutura contivesse ponteiros aninhados, por exemplo, NSValuenão saberia como liberar, copiar ou fazer qualquer coisa com eles - isso os deixaria intactos, copiando o endereço como está.
Justin Spahr-Summers
7

Eu sugeriria que você seguisse o NSValuecaminho, mas se você realmente deseja armazenar structtipos de dados simples em seu NSArray (e outros objetos de coleção em Cocoa), você pode fazer isso - embora indiretamente, usando Core Foundation e ligação gratuita .

CFArrayRef(e sua contraparte mutável CFMutableArrayRef) fornecem ao desenvolvedor mais flexibilidade ao criar um objeto de array. Veja o quarto argumento do inicializador designado:

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

Isso permite que você solicite que o CFArrayRefobjeto use as rotinas de gerenciamento de memória do Core Foundation, nenhuma ou mesmo suas próprias rotinas de gerenciamento de memória.

Exemplo obrigatório:

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

O exemplo acima ignora completamente os problemas com relação ao gerenciamento de memória. A estrutura myStructé criada na pilha e, portanto, é destruída quando a função termina - o array conterá um ponteiro para um objeto que não está mais lá. Você pode contornar isso usando suas próprias rotinas de gerenciamento de memória - daí porque a opção é fornecida a você - mas então você tem que fazer o trabalho duro de contagem de referência, alocação de memória, desalocação e assim por diante.

Eu não recomendaria esta solução, mas a manterei aqui para o caso de ser do interesse de outra pessoa. :-)


O uso de sua estrutura conforme alocado no heap (no lugar da pilha) é demonstrado aqui:

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
fonte
Arrays mutáveis ​​(pelo menos neste caso) são criados em um estado vazio e, portanto, não precisam de um ponteiro para os valores a serem armazenados. Isso é diferente da matriz imutável em que o conteúdo é "congelado" na criação e, portanto, os valores devem ser passados ​​para a rotina de inicialização.
Sedate Alien
@Joe Blow: Essa é uma observação excelente que você fez com relação ao gerenciamento de memória. Você está certo em estar confuso: o exemplo de código que postei acima causaria travamentos misteriosos, dependendo de quando a pilha da função fosse substituída. Comecei a elaborar sobre como minha solução poderia ser usada, mas percebi que estava reimplementando a contagem de referência do próprio Objective-C. Minhas desculpas pelo código compacto - não é uma questão de aptidão, mas de preguiça. Não adianta escrever código que outros não possam ler. :)
Sedate Alien
Se você estivesse feliz em "vazar" (na falta de uma palavra melhor) o struct, certamente poderia alocá-lo uma vez e não liberá-lo no futuro. Incluí um exemplo disso em minha resposta editada. Além disso, não era um erro de digitação myStruct, já que era uma estrutura alocada na pilha, diferente de um ponteiro para uma estrutura alocada na pilha.
Sedate Alien
4

Um método semelhante para adicionar c struct é armazenar o ponteiro e des-referenciar o ponteiro como tal;

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
fonte
3

se você está se sentindo nerd ou realmente tem muitas classes para criar: ocasionalmente é útil construir dinamicamente uma classe objc (ref:) class_addIvar. dessa forma, você pode criar classes objc arbitrárias de tipos arbitrários. você pode especificar campo por campo, ou apenas passar as informações da estrutura (mas isso está praticamente replicando NSData). às vezes útil, mas provavelmente mais um 'fato engraçado' para a maioria dos leitores.

Como eu aplicaria isso aqui?

você pode chamar class_addIvar e adicionar uma variável de instância Megapoint a uma nova classe, ou você pode sintetizar uma variante objc da classe Megapoint em tempo de execução (por exemplo, uma variável de instância para cada campo do Megapoint).

o primeiro é equivalente à classe objc compilada:

@interface MONMegapoint { Megapoint megapoint; } @end

o último é equivalente à classe objc compilada:

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

depois de adicionar os ivars, você pode adicionar / sintetizar métodos.

para ler os valores armazenados na extremidade receptora, use seus métodos sintetizados object_getInstanceVariable, ou valueForKey:(que frequentemente converterão essas variáveis ​​de instância escalar em representações NSNumber ou NSValue).

btw: todas as respostas que você recebeu são úteis, algumas são melhores / piores / inválidas dependendo do contexto / cenário. necessidades específicas relativas à memória, velocidade, facilidade de manutenção, facilidade de transferência ou arquivamento, etc. determinarão qual é o melhor para um determinado caso ... mas não existe uma solução "perfeita" que seja ideal em todos os aspectos. não há 'melhor maneira de colocar uma c-struct em um NSArray', apenas uma 'melhor maneira de colocar uma c-struct em um NSArray para um cenário específico, caso ou conjunto de requisitos ' - o que você teria especificar.

além disso, NSArray é uma interface de array geralmente reutilizável para tipos de ponteiros (ou menores), mas há outros contêineres que são mais adequados para c-structs por muitas razões (std :: vector sendo uma escolha típica para c-structs).

justin
fonte
o histórico das pessoas também entra em jogo ... como você precisa usar essa estrutura geralmente eliminará algumas possibilidades. 4 floats é bastante infalível, mas os layouts de struct variam muito por arquitetura / compilador para usar uma representação de memória contígua (por exemplo, NSData) e esperar que funcione. o serializador objc do pobre homem provavelmente tem o tempo de execução mais lento, mas é o mais compatível se você precisar salvar / abrir / transmitir o Megapoint em qualquer OS X ou dispositivo iOS. A maneira mais comum, em minha experiência, é simplesmente colocar a estrutura em uma classe objc. se você está passando por tudo isso apenas para (cont)
justin
(cont) Se você está passando por todo esse aborrecimento apenas para evitar aprender um novo tipo de coleção - então você deve aprender que o novo tipo de coleção =) std::vector(por exemplo) é mais adequado para manter tipos, estruturas e classes C / C ++ do que NSArray. Ao usar um NSArray dos tipos NSValue, NSData ou NSDictionary, você está perdendo muita segurança de tipo ao adicionar uma tonelada de alocações e sobrecarga de tempo de execução. Se você quiser ficar com C, eles geralmente usam malloc e / ou matrizes na pilha ... mas std::vectoresconde a maioria das complicações de você.
justin
de fato, se você deseja manipulação / iteração de array como mencionou - stl (parte das bibliotecas padrão c ++) é ótimo para isso. você tem mais tipos para escolher (por exemplo, se inserir / remover é mais importante do que tempos de acesso de leitura), e toneladas de formas existentes de manipular os contêineres. também - não é memória vazia em c ++ - os contêineres e funções de modelo são compatíveis com o tipo e verificados na compilação - muito mais seguro do que puxar uma string de byte arbitrária de representações NSData / NSValue. eles também têm verificação de limites e, principalmente, gerenciamento automático de memória. (cont.)
justin
(cont.) se você espera ter muito trabalho de baixo nível como este, então você deve apenas aprender agora - mas levará tempo para aprender. ao envolver tudo isso em representações objc, você está perdendo muito desempenho e segurança de tipo, enquanto se faz escrever muito mais código clichê para acessar e interpretar os contêineres e seus valores (Se 'Assim, para ser muito específico ...' é exatamente o que você deseja fazer).
justin
é apenas mais uma ferramenta à sua disposição. pode haver complicações integrando objc com c ++, c ++ com objc, c com c ++ ou qualquer uma das várias outras combinações. adicionar recursos de idioma e usar vários idiomas tem um custo pequeno em qualquer caso. vai de todas as maneiras. por exemplo, os tempos de construção aumentam ao compilar como objc ++. da mesma forma, essas fontes não são reutilizadas em outros projetos tão facilmente. claro, você pode reimplementar os recursos de linguagem ... mas essa nem sempre é a melhor solução. integrar c ++ em um projeto objc é bom, é tão 'confuso' quanto usar fontes objc e c no mesmo projeto. (cont
justin
3

seria melhor usar o serializador objc do pobre se você estiver compartilhando esses dados em vários abis / arquiteturas:

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

... particularmente se a estrutura pode mudar ao longo do tempo (ou por plataforma de destino). não é tão rápido quanto outras opções, mas é menos provável que falhe em algumas condições (que você não especificou como importantes ou não).

se a representação serializada não sair do processo, então o tamanho / ordem / alinhamento de estruturas arbitrárias não deve mudar, e há opções que são mais simples e rápidas.

em ambos os casos, você já está adicionando um objeto ref-counted (em comparação com NSData, NSValue), então ... criar uma classe objc que contenha o Megapoint é a resposta certa em muitos casos.

justin
fonte
@Joe Blow algo que realiza serialização. para referência: en.wikipedia.org/wiki/Serialization , parashift.com/c++-faq-lite/serialization.html , bem como o "Archives and Serializations Programming Guide" da Apple.
justin
assumindo que o arquivo xml representa algo apropriadamente, então sim - é uma forma comum de serialização legível por humanos.
justin
0

Eu sugiro que você use std :: vector ou std :: list para tipos C / C ++, porque no início é apenas mais rápido do que NSArray e, no segundo, se não houver velocidade suficiente para você - você sempre pode criar o seu próprio alocadores para contêineres STL e torná-los ainda mais rápidos. Todos os mecanismos modernos de jogos, física e áudio para dispositivos móveis usam contêineres STL para armazenar dados internos. Só porque eles são muito rápidos.

Se não for para você - há boas respostas dos caras sobre NSValue - acho que é mais aceitável.

Evgen Bodunov
fonte
STL é uma biblioteca parcialmente incluída na Biblioteca Padrão C ++. en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Evgen Bodunov
Essa é uma afirmação interessante. Você tem um link para um artigo sobre a vantagem de velocidade dos contêineres STL em relação às classes de contêiner Cocoa?
Sedate Alien
aqui está uma leitura interessante sobre NSCFArray vs std :: vector: ridiculousfish.com/blog/archives/2005/12/23/array no exemplo em sua postagem, a maior perda é (normalmente) criar uma representação de objeto objc por elemento (por exemplo , NSValue, NSData ou tipo Objc contendo Megapoint requer uma alocação e inserção no sistema de contagem ref). você poderia realmente evitar isso usando a abordagem Sedate Alien para armazenar um Megapoint em um CFArray especial que usa um armazenamento de apoio separado de Megapoints alocados contiguamente (embora nenhum exemplo ilustre essa abordagem). (cont)
justin
mas, em seguida, usar o NSCFArray versus o vetor (ou outro tipo de stl) incorrerá em sobrecarga adicional para despacho dinâmico, chamadas de função adicionais que não são sequenciais, uma tonelada de segurança de tipo e muitas chances de o otimizador entrar em ação ... que o artigo se concentra apenas em inserir, ler, percorrer, excluir. as chances são de que você não obterá nada mais rápido do que um c-array alinhado de 16 bytes Megapoint pt[8];- esta é uma opção em c ++ e contêineres C ++ especializados (por exemplo, std::array) - observe também que o exemplo não adiciona o alinhamento especial (16 bytes foram escolhidos porque é do tamanho de um Megapoint). (cont.)
justin
std::vectorirá adicionar uma pequena quantidade de overhead a isso, e uma alocação (se você souber o tamanho de que precisará) ... mas isso está mais próximo do metal do que mais de 99,9% dos gabinetes precisam. normalmente, você apenas usaria um vetor, a menos que o tamanho seja fixo ou tenha um máximo razoável.
justin
0

Em vez de tentar colocar c struct em um NSArray, você pode colocá-los em um NSData ou NSMutableData como um array ac de structs. Para acessá-los, você deve fazer

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

ou para definir então

struct MyStruct    * theStruct = (struct MyStruct*)[myData mutableBytes];
theStruct[2].integerNumber = 10;
Dia de Nathan
fonte
0

Para sua estrutura, você pode adicionar um atributo objc_boxable e usar a @()sintaxe para colocar sua estrutura na instância NSValue sem chamar 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
fonte
-2

Um objeto Obj C é apenas uma estrutura C com alguns elementos adicionados. Portanto, basta criar uma classe personalizada e você terá o tipo de estrutura C que um NSArray requer. Qualquer estrutura C que não tenha o fragmento extra que um NSObject inclui em sua estrutura C será indigesta para um NSArray.

Usar NSData como um wrapper pode apenas armazenar uma cópia dos structs e não dos structs originais, se isso fizer alguma diferença para você.

hotpaw2
fonte
-3

Você pode usar classes NSObject diferentes das C-Structures para armazenar informações. E você pode armazenar facilmente esse NSObject no NSArray.

Satya
fonte