Qual é a melhor maneira de resolver uma colisão de namespace do Objective-C?

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

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

Ok, se isso é uma colisão entre uma das suas próprias classs e uma de uma estrutura externa que você está usando, você pode facilmente alterar a nomenclatura de sua class, não é grande coisa. Mas e se você usar dois frameworks externos, ambos os frameworks para os quais você não possui a fonte e que você não pode mudar? Seu aplicativo vincula-se a ambos e você recebe conflitos de nome. Como você resolveria isso? Qual é a melhor maneira de contorná-los de tal forma que você ainda pode usar as duas classs?

Em C você pode contornar isso não ligando diretamente para a biblioteca, em vez disso você carrega a biblioteca em tempo de execução, usando dlopen (), então encontre o símbolo que você está procurando usando dlsym () e atribua a um símbolo global (que você pode nomear como quiser) e depois acessá-lo através deste símbolo global. Por exemplo, se você tem um conflito porque alguma biblioteca C tem uma function chamada open (), você pode definir uma variável chamada myOpen e apontar para a function open () da biblioteca, assim quando você quiser usar o sistema open () , você só usa open () e quando você quer usar o outro, você acessa através do myOpen identifier.

É algo semelhante possível em Objective-C e se não, existe alguma outra solução inteligente e complicada que você pode usar para resolver conflitos de namespace? Alguma ideia?


Atualizar:

Apenas para esclarecer isso: respostas que sugiram como evitar colisões de namespace antecipadamente ou como criar um melhor namespace são certamente bem-vindas; no entanto, não vou aceitá-los como resposta, uma vez que não resolvem o meu problema. Eu tenho duas bibliotecas e seus nomes de class colidem. 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 evitado com antecedência não vai ajudar mais. Eu posso encaminhá-los para os desenvolvedores desses frameworks e espero que eles escolham um melhor namespace no futuro, mas por enquanto eu estou procurando uma solução para trabalhar com os frameworks agora mesmo dentro de um único aplicativo. Alguma solução para tornar isso possível?

Se você não precisa usar classs de ambas as estruturas ao mesmo tempo, e você está direcionando plataformas que suportam descarga NSBundle (OS X 10.4 ou posterior, sem suporte a GNUStep), e desempenho realmente não é um problema para você, eu acredito que você poderia carregar uma estrutura toda vez que precisar usar uma class dela e depois descarregá-la e carregar a outra quando precisar usar a outra estrutura.

Minha ideia inicial era usar o NSBundle para carregar um dos frameworks, então copiar ou renomear as classs dentro daquele framework, e então carregar o outro framework. Existem dois problemas com isso. Primeiro, eu não consegui encontrar uma function para copiar os dados apontados para renomear ou copiar uma class, e quaisquer outras classs naquele primeiro framework que referenciam a class renomeada agora referenciariam a class do outro framework.

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

EDIT: A diferença fundamental entre os tempos de execução C e Objective-C é, como eu entendo, quando as bibliotecas são carregadas, as funções nessas bibliotecas contêm pointers para quaisquer símbolos que eles referenciam, enquanto que em Objective-C, eles contêm representações de string do nomes dos símbolos. Assim, no seu exemplo, você pode usar o dlsym para obter o endereço do símbolo na memory e anexá-lo a outro símbolo. O outro código na biblioteca ainda funciona porque você não está alterando o endereço do símbolo original. O Objective-C usa uma tabela de consulta para mapear nomes de classs para endereços, e é um mapeamento de 1-1, então você não pode ter duas classs com o mesmo nome. Assim, para carregar as duas classs, uma delas deve ter seu nome alterado. No entanto, quando outras classs precisarem acessar uma das classs com esse nome, elas solicitarão à tabela de consulta seu endereço, e a tabela de consulta nunca retornará o endereço da class renomeada, dado o nome da class original.

Prefixar suas classs 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 . Meu favorito é a @compatibility_alias de compilador Objective-C do @compatibility_alias (descrita aqui ). Você pode usar @compatibility_alias para “renomear” uma class, permitindo que você nomeie sua class usando o FQDN ou algum prefixo desse tipo:

 @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ê poderia prefixar todas as suas classs com um prefixo exclusivo, como o FQDN, e criar um header com todas as @compatibility_alias (imagino que você poderia gerar automaticamente o header).

A desvantagem do prefixo assim é que você tem que inserir o nome da class true (por exemplo, COM_WHATEVER_ClassName acima) em qualquer coisa que precise do nome da class de uma string além do compilador. Notavelmente, @compatibility_alias é uma diretiva de compilador, não uma function de tempo de execução, portanto, NSClassFromString(ClassName) falhará (return nil ) – você terá que usar NSClassFromString(COM_WHATERVER_ClassName) . Você pode usar o ibtool por meio da fase de construção para modificar nomes de classs em um nib / xib do Interface Builder, para que não seja necessário gravar o COM_WHATEVER completo _… no Interface Builder.

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

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

  1. Em qualquer caso, informe os desenvolvedores de ambas as estruturas do conflito e deixe claro que sua falha em evitar e / ou lidar com isso está causando problemas reais de negócios, o que pode resultar em perda de receita de negócios se não for resolvido. Enfatize que, embora a resolução de conflitos existentes em uma base por class seja uma correção menos invasiva, alterar o prefixo deles (ou usar um deles se não estiverem no momento e envergonhá-los!) É a melhor maneira de garantir que eles não veja o mesmo problema novamente.
  2. Se os conflitos de nomenclatura estiverem limitados a um conjunto razoavelmente pequeno de classs, veja se você pode contornar apenas essas classs, especialmente se uma das classs 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 classs conflitantes. Se não, seja franco sobre o fato de que sua inflexibilidade está reduzindo seu ROI de usar o framework deles. Não se sinta mal por ser agressivo – o cliente está sempre certo. 😉
  3. Se uma estrutura é mais “dispensável”, você pode considerar substituí-la por outra estrutura (ou combinação de código), seja de terceiros ou homebrew. (O último é o pior caso indesejável, uma vez que ele certamente incorrerá em custos adicionais de negócios, tanto para desenvolvimento quanto para manutenção.) Se o fizer, informe o fornecedor sobre essa estrutura exatamente por que você decidiu não usar sua estrutura.
  4. Se ambos os frameworks forem considerados igualmente indispensáveis ​​para a sua aplicação, explore maneiras de fatorar o uso de um deles em um ou mais processos separados, talvez se comunicando via DO como Louis Gerbarg sugeriu. Dependendo do grau de comunicação, isso pode não ser tão ruim quanto você poderia esperar. Vários programas (inclusive o QuickTime, acredito) usam essa abordagem para fornecer segurança mais granular, usando os perfis do sandbox Seatbelt no Leopard , de forma que apenas um subconjunto específico de 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

Eu estou supondo que as taxas de licenciamento, termos e durações podem impedir a ação instantânea em qualquer um desses pontos. Espero que você consiga resolver o conflito o mais rápido possível. Boa sorte!

Isso é bruto, mas você pode usar objects distribuídos para manter uma das classs somente em um endereço de programas subordinados e RPC para ele. Isso vai ficar confuso se você estiver passando uma tonelada de coisas para frente e para trás (e pode não ser possível se ambas as classs estiverem manipulando diretamente as views, etc).

Existem outras soluções possíveis, mas muitas delas dependem da situação exata. Em particular, você está usando os tempos de execução modernos ou legados, sua arquitetura simples ou básica, 32 ou 64 bits, que versões do SO você está segmentando, vinculando dinamicamente, vinculando estaticamente ou tem uma opção? ok para fazer algo que possa exigir manutenção para novas atualizações de software.

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

  1. Não vincular diretamente uma das bibliotecas
  2. Implemente uma versão alternativa das rotinas de tempo de execução objc que altere o nome no momento do carregamento ( verifique o projeto objc4 , o que exatamente você precisa fazer depende de várias das perguntas que fiz acima, 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 methods normais, ela passará pela rotina de vinculação corrigida e obterá seu className alterado

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

Você já pensou em usar as funções de tempo de execução (/usr/include/objc/runtime.h) para clonar uma das classs conflitantes em uma class que não colide e, em seguida, carregar a estrutura de class de colisão? (isso exigiria que as estruturas de colisão fossem carregadas em horários diferentes para funcionar.)

Você pode inspecionar as classs ivars, methods (com nomes e endereços de implementação) e nomes com o tempo de execução, e criar seu próprio dinamicamente para ter o mesmo layout ivar, nomes de methods / endereços de implementação e diferir apenas pelo nome (para evitar o colisão)

Situações desesperadas exigem medidas desesperadas. Você já considerou hackear o código object (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 comprimento de 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 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 ideia minimizar a diferença nas grafias, de modo que, se os símbolos estiverem em uma ordem classificada em uma tabela, a renomeação não moverá as coisas fora de ordem. Coisas como a pesquisa binária ficam chateadas se a matriz que estão pesquisando não estiver em ordem de sorting conforme o esperado.

Parece que o problema é que você não pode referenciar arquivos de headers de ambos os sistemas na mesma unidade de tradução (arquivo de origem). Se você criar invólucros objective-c ao redor das bibliotecas (tornando-as mais utilizáveis ​​no processo) e apenas #include os headers de cada biblioteca na implementação das classs de invólucro, isso separaria efetivamente as colisões de nomes.

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

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

 @compatibility_alias NewAliasClass OriginalClass; 

No entanto, isso não resolverá nenhuma das colisões de namespace enums, typedefs ou protocolo . Além disso, não joga bem com os @class forward @class da class original. Como a maioria dos frameworks virá com essas coisas que não são de class, como typedefs, você provavelmente não conseguirá corrigir o problema de namespace com apenas compatibility_alias.

Eu olhei para um problema semelhante ao seu , mas eu tinha access à fonte e estava construindo os frameworks. A melhor solução que encontrei para isso foi o uso de @compatibility_alias condicionalmente com #defines para suportar os enums / typedefs / protocols / etc. Você pode fazer isso condicionalmente na unidade de compilation do header em questão para minimizar o risco de expandir o material na outra estrutura de colisão.

Prefixar os arquivos é a solução mais simples que conheço. O Cocoadev tem uma página de namespace que é um esforço da comunidade para evitar colisões de namespace. Sinta-se à vontade para adicionar o seu próprio a essa lista, acredito que seja para isso.

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

Se você tiver uma colisão, sugiro que pense muito sobre como refatorar um dos frameworks do seu aplicativo. Ter uma colisão sugere que os dois estão fazendo coisas semelhantes do jeito que é, e você provavelmente poderia contornar usando uma estrutura extra simplesmente refatorando sua aplicação. Isso não apenas resolveria seu problema de namespace, mas tornaria seu código mais robusto, mais fácil de manter e mais eficiente.

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

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

 cc foo.o -ldog bar.o -lcat 

Se foo.o e bar.o referência ao rat libdog , a libdog resolverá o rat libcat e a libcat resolverá o rat bar.o

Apenas um pensamento .. não testado ou comprovado e poderia ser o caminho da marca, mas você já pensou em escrever um adaptador para a class que você usa a partir do mais simples dos frameworks .. ou pelo menos suas interfaces?

Se você fosse escrever um wrapper em torno do framework mais simples (ou aquele que é o interface que você acessa menos), não seria possível compilar esse wrapper em uma biblioteca. Tendo em vista que a biblioteca é pré-compilada e apenas seus headers precisam ser distribuídos, você estaria efetivamente escondendo o framework subjacente e estaria livre para combiná-lo com o segundo framework com clashing.

Eu aprecio, é claro, que provavelmente haverá momentos em que você precisa usar classs de ambas as estruturas ao mesmo tempo, no entanto, você poderia fornecer fábricas para outros adaptadores de class dessa estrutura. Na parte de trás desse ponto, eu acho que você precisaria de um pouco de refatoração para extrair as interfaces que você está usando de ambos os frameworks, o que deveria fornecer um bom ponto de partida para você construir seu wrapper.

Você poderia construir sobre a biblioteca como você e quando você precisar de mais funcionalidades da biblioteca quebrada, e simplesmente recompilar quando você mudar.

Mais uma vez, de forma alguma comprovada, mas senti como adicionar uma perspectiva. espero que ajude 🙂

Se você tiver duas estruturas que tenham o mesmo nome de function, poderá tentar carregar dinamicamente as estruturas. Vai ser deselegante, mas possível. Como fazer isso com classs Objective-C, não sei. Eu estou supondo que a class NSBundle terá methods que carregarão uma class específica.