Qual é a melhor maneira de resolver uma colisão de espaço de nome Objective-C?


174

Objective-C não possui namespaces; é muito parecido com C, tudo está dentro de um espaço para nome global. A prática comum é prefixar as classes com as iniciais; por exemplo, se você estiver trabalhando na IBM, poderá prefixá-las com "IBM"; se você trabalha na Microsoft, pode usar "MS"; e assim por diante. Às vezes, as iniciais se referem ao projeto, por exemplo, o Adium prefixa as classes com "AI" (como não há nenhuma empresa por trás disso, você pode pegar as iniciais). A Apple prefixa as classes com o NS e diz que esse prefixo é reservado apenas para a Apple.

Até aqui tudo bem. Mas anexar 2 a 4 letras a um nome de classe na frente é um espaço para nome muito, muito limitado. Por exemplo, MS ou AI podem ter significados completamente diferentes (AI pode ser Inteligência Artificial, por exemplo) e algum outro desenvolvedor pode decidir usá-los e criar uma classe com o mesmo nome. Bang , colisão de namespace.

Ok, se essa é uma colisão entre uma de suas próprias classes e uma de uma estrutura externa que você está usando, você pode facilmente mudar o nome da sua classe, não é grande coisa. Mas e se você usar duas estruturas externas, ambas para as quais você não tem a fonte e para as quais não pode mudar? Seu aplicativo é vinculado a ambos e você obtém conflitos de nome. Como você resolveria isso? Qual é a melhor maneira de contorná-los de tal maneira que você ainda possa usar as duas classes?

Em C, você pode solucionar esses problemas não vinculando diretamente à biblioteca; em vez disso, carrega a biblioteca em tempo de execução, usando dlopen (), encontre o símbolo que procura usando dlsym () e atribua-o a um símbolo global (que você pode nomear como quiser) e acessá-lo através deste símbolo global. Por exemplo, se você tiver um conflito porque alguma biblioteca C possui uma função chamada open (), você pode definir uma variável chamada myOpen e apontar para a função open () da biblioteca, portanto, quando desejar usar o sistema open () , você apenas usa open () e quando deseja usar o outro, acessa-o através do identificador myOpen.

Existe algo semelhante possível no Objective-C e, caso contrário, existe alguma outra solução inteligente e complicada que você possa usar para resolver conflitos de namespace? Alguma ideia?


Atualizar:

Apenas para esclarecer isso: respostas que sugerem como evitar colisões com espaço para nome antecipadamente ou como criar um espaço para nome melhor são certamente bem-vindas; no entanto, não os aceitarei como resposta, pois eles não resolvem o meu problema. Eu tenho duas bibliotecas e seus nomes de classe colidem. Eu não posso mudá-los; Eu não tenho a fonte de nenhum dos dois. A colisão já está lá e dicas sobre como poderia ter sido evitada com antecedência não ajudarão mais. Posso encaminhá-los aos desenvolvedores dessas estruturas e espero que eles escolham um espaço para nome melhor no futuro, mas, por enquanto, estou procurando uma solução para trabalhar com as estruturas agora mesmo em um único aplicativo. Alguma solução para tornar isso possível?


7
Você tem uma boa pergunta (o que fazer se precisar de duas estruturas com uma colisão de nomes), mas ela está oculta no texto. Revise para tornar mais claro e evitará respostas simplistas como a que você tem agora.
benzado

4
Esta é a minha maior reclamação com o design atual da linguagem Objective-C. Veja as respostas abaixo; aqueles que realmente abordam a questão (descarregamento do NSBundle, usando DO, etc) são hackers hediondos que simplesmente não devem ser necessários para algo tão trivial como evitar um conflito no namespace.
0000 erikprice

@erikprice: Amém. Estou aprendendo obj-c, e atingi esse mesmo problema. Vim aqui à procura de uma solução simples .... coxo.
Dave Mateer 5/02

1
Para o registro, tecnicamente, C e Objective-C fornecem suporte para vários espaços de nome - embora não exatamente o que o OP está procurando. Veja objectivistc.tumblr.com/post/3340816080/…

Hmm, eu não sabia disso. Uma decisão terrível sobre o design, não?
Nico

Respostas:


47

Se você não precisa usar classes de ambas as estruturas ao mesmo tempo e está direcionando plataformas que suportam o descarregamento do NSBundle (OS X 10.4 ou posterior, sem suporte ao GNUStep), e o desempenho realmente não é um problema para você, acredito que você pode carregar uma estrutura toda vez que precisar usar uma classe e, em seguida, descarregá-la e carregar a outra quando precisar usar a outra estrutura.

Minha idéia inicial era usar o NSBundle para carregar uma das estruturas, copiar ou renomear as classes dentro dessa estrutura e, em seguida, carregar a outra estrutura. Existem dois problemas com isso. Primeiro, não consegui encontrar uma função para copiar os dados apontados para renomear ou copiar uma classe, e qualquer outra classe nessa primeira estrutura que faça referência à classe renomeada agora faria referência à classe da outra estrutura.

Você não precisaria copiar ou renomear uma classe se houvesse uma maneira de copiar os dados apontados por um IMP. Você pode criar uma nova classe e depois copiar sobre ivars, métodos, propriedades e categorias. Muito mais trabalho, mas é possível. No entanto, você ainda teria um problema com as outras classes na estrutura que referenciam a classe errada.

EDIT: A diferença fundamental entre os tempos de execução C e Objective-C é, pelo que entendi, quando as bibliotecas são carregadas, as funções nessas bibliotecas contêm ponteiros para qualquer símbolo a que se referem, enquanto que no Objective-C, elas contêm representações de string do nomes desses símbolos. Assim, no seu exemplo, você pode usar o dlsym para obter o endereço do símbolo na memória e anexá-lo a outro símbolo. O outro código da biblioteca ainda funciona porque você não está alterando o endereço do símbolo original. O Objective-C usa uma tabela de pesquisa para mapear nomes de classes para endereços, e é um mapeamento 1-1, portanto você não pode ter duas classes com o mesmo nome. Portanto, para carregar as duas classes, uma delas deve ter seu nome alterado. No entanto, quando outras classes precisam acessar uma das classes com esse nome,


5
Acredito que o descarregamento de pacotes não era suportado até 10.5 ou posterior.
Quinn Taylor

93

Prefixar suas classes com um prefixo exclusivo é fundamentalmente a única opção, mas existem várias maneiras de tornar isso menos oneroso e feio. Há uma longa discussão de opções aqui . O meu favorito é a @compatibility_aliasdiretiva de compilador Objective-C (descrita aqui ). Você pode usar @compatibility_aliaspara "renomear" uma classe, permitindo que você nomeie sua classe usando o FQDN ou algum prefixo:

@interface COM_WHATEVER_ClassName : NSObject
@end

@compatibility_alias ClassName COM_WHATEVER_ClassName
// now ClassName is an alias for COM_WHATEVER_ClassName

@implementation ClassName //OK
//blah
@end

ClassName *myClass; //OK

Como parte de uma estratégia completa, você pode prefixar todas as suas classes com um prefixo exclusivo, como o FQDN, e criar um cabeçalho com todos os @compatibility_alias(imagino que você possa gerar automaticamente o referido cabeçalho).

A desvantagem de prefixar assim é que você deve inserir o nome verdadeiro da classe (por exemplo, COM_WHATEVER_ClassNameacima) em qualquer coisa que precise do nome da classe de uma string além do compilador. Notavelmente, @compatibility_aliasé uma diretiva de compilador, não uma função de tempo de execução, portanto NSClassFromString(ClassName)falhará (retornará nil) - você terá que usar NSClassFromString(COM_WHATERVER_ClassName). É possível usar a ibtoolfase de construção para modificar os nomes de classe em um nib / xib do Interface Builder, para que você não precise gravar o COM_WHATEVER _... completo no Interface Builder.

Advertência final: como essa é uma diretiva de compilador (e obscura), ela pode não ser portátil entre os compiladores. Em particular, não sei se funciona com o front-end Clang do projeto LLVM, embora deva funcionar com o LLVM-GCC (LLVM usando o front-end do GCC).


4
Voto positivo para asias_compatibilidade, eu não sabia disso! Obrigado. Mas isso não resolve o meu problema. Não estou no controle para alterar nenhum prefixo de qualquer estrutura que estou usando, apenas os tenho em formato binário e eles colidem. O que devo fazer sobre isso?
Mecki 7/10/08

Em qual arquivo (.h ou .m) deve ir a linha @compatibility_alias?
Alex Basson

1
@Alex_Basson @compatibility_alias provavelmente deve ir no cabeçalho para ficar visível onde quer que a declaração @interface esteja visível.
Barry Wark

Gostaria de saber se você pode usar @compatibility_alias com nomes de protocolos ou definições de typedef, ou qualquer outra coisa para esse assunto?
Ali

Bons pensamentos aqui. O método swizzling pode ser usado para impedir que NSClassFromString (ClassName) retorne zero para classes com alias? Provavelmente seria difícil encontrar todos os métodos que levaram um nome de classe.
Dickey Singh

12

Várias pessoas já compartilharam algum código complicado e inteligente que pode ajudar a resolver o problema. Algumas sugestões podem funcionar, mas todas são inferiores ao ideal e algumas são absolutamente desagradáveis ​​de implementar. (Às vezes, hacks feios são inevitáveis, mas tento evitá-los sempre que posso.) Do ponto de vista prático, aqui estão minhas sugestões.

  1. De qualquer forma, informe os desenvolvedores de ambas as estruturas do conflito e deixe claro que a falha em evitar e / ou lidar com isso está causando problemas reais de negócios, que podem resultar em perda de receita comercial se não forem resolvidos. Enfatize que, embora a solução de conflitos existentes por classe seja uma solução menos invasiva, a alteração completa do prefixo (ou o uso de um se não estiver presente no momento e a vergonha!) É a melhor maneira de garantir que não veja o mesmo problema novamente.
  2. Se os conflitos de nomenclatura estiverem limitados a um conjunto razoavelmente pequeno de classes, veja se você pode contornar apenas essas classes, especialmente se uma das classes conflitantes não estiver sendo usada pelo seu código, direta ou indiretamente. Nesse caso, verifique se o fornecedor fornecerá uma versão personalizada da estrutura que não inclua as classes conflitantes. Caso contrário, seja franco com o fato de que a inflexibilidade deles reduz o ROI do uso da estrutura. Não se sinta mal por ser agressivo dentro da razão - o cliente está sempre certo. ;-)
  3. Se uma estrutura for mais "dispensável", considere substituí-la por outra estrutura (ou combinação de código), de terceiros ou de homebrew. (O último é o pior caso indesejável, pois certamente incorrerá em custos comerciais adicionais, tanto para desenvolvimento quanto para manutenção.) Se o fizer, informe o fornecedor dessa estrutura exatamente por que decidiu não usá-la.
  4. Se ambas as estruturas forem consideradas igualmente indispensáveis ​​ao seu aplicativo, explore maneiras de levar em consideração o uso de uma delas para um ou mais processos separados, talvez se comunicando via DO, como sugeriu Louis Gerbarg. Dependendo do grau de comunicação, isso pode não ser tão ruim quanto o esperado. Vários programas (incluindo o QuickTime, acredito) usam essa abordagem para fornecer segurança mais granular fornecida usando perfis de sandbox do Seatbelt no Leopard , de modo que apenas um subconjunto específico do seu código tenha permissão para executar operações críticas ou confidenciais. O desempenho será uma troca, mas pode ser sua única opção

Suponho que taxas, termos e durações de licenciamento possam impedir ações instantâneas em qualquer um desses pontos. Espero que você consiga resolver o conflito o mais rápido possível. Boa sorte!


8

Isso é grosseiro, mas você pode usar objetos distribuídos para manter uma das classes apenas no endereço de um programa subordinado e no RPC. Isso ficará confuso se você estiver passando uma tonelada de coisas para frente e para trás (e pode não ser possível se as duas classes estiverem manipulando diretamente visualizações, etc.).

Existem outras soluções em potencial, mas muitas delas dependem da situação exata. Em particular, você está usando os tempos de execução modernos ou herdados, sua arquitetura única ou de 32 ou 64 bits, quais versões do sistema operacional você está almejando, vinculando dinamicamente, vinculando estaticamente ou tem uma opção, e é potencialmente Não há problema em fazer algo que possa exigir manutenção para novas atualizações de software.

Se você está realmente desesperado, o que você pode fazer é:

  1. Não vincular diretamente a uma das bibliotecas
  2. Implemente uma versão alternativa das rotinas de tempo de execução objc que alteram o nome no momento do carregamento (faça o checkout do projeto objc4 , o que exatamente você precisa fazer depende de várias perguntas feitas anteriormente, mas deve ser possível, independentemente das respostas. )
  3. Use algo como mach_override para injetar sua nova implementação
  4. Carregue a nova biblioteca usando métodos normais, ela passará pela rotina do vinculador corrigido e terá seu className alterado

O exposto acima será bastante trabalhoso, e se você precisar implementá-lo em vários arcos e em diferentes versões de tempo de execução, será muito desagradável, mas definitivamente pode ser feito para funcionar.


4

Você já pensou em usar as funções de tempo de execução (/usr/include/objc/runtime.h) para clonar uma das classes conflitantes em uma classe sem colisão e depois carregar a estrutura de classe em colisão? (isso exigiria que as estruturas em colisão fossem carregadas em momentos diferentes para funcionar.)

É possível inspecionar as classes ivars, métodos (com nomes e endereços de implementação) e nomes com o tempo de execução e criar seus próprios dinamicamente para ter o mesmo layout ivar, nomes de métodos / endereços de implementação e diferir apenas por nome (para evitar o colisão)


3

Situações desesperadas exigem medidas desesperadas. Você já pensou em invadir o código do objeto (ou arquivo de biblioteca) de uma das bibliotecas, alterando o símbolo de colisão para um nome alternativo - do mesmo tamanho, mas com uma ortografia diferente (mas, recomendação, o mesmo tamanho do nome)? Inerentemente desagradável.

Não está claro se o seu código está chamando diretamente as duas funções com o mesmo nome, mas com implementações diferentes ou se o conflito é indireto (nem está claro se isso faz alguma diferença). No entanto, há pelo menos uma chance externa de que a renomeação funcione. Também pode ser uma idéia minimizar a diferença nas grafias, para que, se os símbolos estiverem em uma ordem classificada em uma tabela, a renomeação não altere as coisas. Coisas como a pesquisa binária ficam chateadas se a matriz que eles estão pesquisando não está na ordem de classificação conforme o esperado.


Alterar a biblioteca no disco está fora de questão, porque a licença não permitirá isso. Alterar os símbolos na memória seria possível, mas não vejo como isso pode ser feito (carregar a lib na memória, modificá-la e depois passá-la para o vinculador dinâmico ... não há método para isso).
Mecki

3
OK - hora de decidir qual das duas bibliotecas é menos importante e encontrar uma substituição. E explique a quem você paga, mas que está abandonando o motivo de estar mudando por causa dessa colisão e eles podem reter seus negócios, corrigindo o seu problema.
27611 Jonathan Leffler

2

@compatibility_alias será capaz de resolver conflitos de namespace de classe, por exemplo

@compatibility_alias NewAliasClass OriginalClass;

No entanto, isso não resolverá nenhuma das enumerações, typedefs ou colisões de namespace de protocolo . Além disso, ele não funciona bem com @classdecls avançados da classe original. Como a maioria das estruturas virá com coisas que não são de classe, como typedefs, você provavelmente não conseguirá corrigir o problema de espaço para nome apenas com a compatibilidade_alias.

Eu olhei para um problema semelhante ao seu , mas tinha acesso à fonte e estava construindo as estruturas. A melhor solução que encontrei para isso foi usar @compatibility_aliascondicionalmente com #defines para suportar enumerações / typedefs / protocolos / etc. Você pode fazer isso condicionalmente na unidade de compilação do cabeçalho em questão para minimizar o risco de expandir coisas na outra estrutura em colisão.


1

Parece que o problema é que você não pode fazer referência a arquivos de cabeçalhos de ambos os sistemas na mesma unidade de tradução (arquivo de origem). Se você criar wrappers de objetivo-c em torno das bibliotecas (tornando-os mais utilizáveis ​​no processo) e #incluir apenas os cabeçalhos de cada biblioteca na implementação das classes de wrapper, isso separaria efetivamente as colisões de nomes.

Eu não tenho experiência suficiente com isso no objetivo-c (apenas começando), mas acredito que é isso que eu faria em C.


1
Mas você ainda não enfrentaria colisões se tentasse incluir os dois cabeçalhos do wrapper no mesmo arquivo (já que cada um deles precisaria incluir os cabeçalhos de suas respectivas estruturas)?
Wilco

0

Prefixar os arquivos é a solução mais simples que eu conheço. Cocoadev tem uma página de espaço para nome, que é um esforço da comunidade para evitar colisões de espaço para nome. Sinta-se livre para adicionar seu próprio a esta lista, acredito que é para isso.

http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix


Como a prefixação dos arquivos ajuda se os objetos definidos nos arquivos causam o problema? Certamente posso manipular os arquivos h, mas o vinculador não encontrará mais os objetos quando estiver gostando da estrutura: - /
Mecki 7/10/08

Acredito que isso seria uma melhor prática do que uma solução para o problema inicial.
Ali

Isso não faz nada endereço a questão
Madbreaks

0

Se você tiver uma colisão, sugiro que pense bem em como refatorar uma das estruturas do seu aplicativo. Ter uma colisão sugere que os dois estão fazendo coisas semelhantes e é provável que você possa usar uma estrutura extra simplesmente refatorando seu aplicativo. Isso não apenas resolveria o problema do espaço para nome, como também tornaria o seu código mais robusto, mais fácil de manter e mais eficiente.

Sobre uma solução mais técnica, se eu estivesse na sua posição, essa seria minha escolha.


0

Se a colisão ocorrer apenas no nível do link estático, você poderá escolher qual biblioteca é usada para resolver os símbolos:

cc foo.o -ldog bar.o -lcat

Se foo.oe bar.oambos referência o símbolo ratem seguida, libdogirá resolver foo.o's rate libcatvai resolver bar.oé rat.


0

Apenas um pensamento .. não testado ou comprovado e pode ser um marco, mas você já pensou em escrever um adaptador para a classe que você usa a partir das estruturas mais simples .. ou pelo menos suas interfaces?

Se você escrevesse um invólucro em torno das estruturas mais simples (ou aquelas com menos acesso às interfaces), não seria possível compilar esse invólucro em uma biblioteca. Dado que a biblioteca é pré-compilada e apenas seus cabeçalhos precisam ser distribuídos, você estaria efetivamente ocultando a estrutura subjacente e estaria livre para combiná-la com a segunda estrutura com conflito.

Compreendo, é claro, que provavelmente haverá momentos em que você precisará usar as classes de ambas as estruturas ao mesmo tempo; no entanto, você poderá fornecer fábricas para outros adaptadores de classe dessa estrutura. Na parte de trás desse ponto, acho que você precisaria de um pouco de refatoração para extrair as interfaces que você está usando das duas estruturas, o que deve fornecer um bom ponto de partida para você construir seu wrapper.

Você pode desenvolver a biblioteca como você e quando precisar de mais funcionalidades da biblioteca agrupada e simplesmente recompilar quando alterar.

Novamente, de maneira alguma comprovada, mas senti vontade de acrescentar uma perspectiva. espero que ajude :)


-1

Se você tiver duas estruturas com o mesmo nome de função, tente carregar dinamicamente as estruturas. Será deselegante, mas possível. Como fazer isso com as classes Objective-C, eu não sei. Suponho que a NSBundleclasse terá métodos que carregarão uma classe específica.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.