¿Cuál es la mejor manera de resolver una colisión de espacio de nombres Objective-C?


Objective-C no tiene espacios de nombres; es muy parecido a C, todo está dentro de un espacio de nombres global. La práctica común es prefijar las clases con iniciales, por ejemplo, si está trabajando en IBM, podría prefijarlas con "IBM"; si trabaja para Microsoft, podría usar" MS"; y así sucesivamente. A veces las iniciales se refieren al proyecto, por ejemplo, Adium prefigura las clases con " AI " (ya que no hay compañía detrás de él de la que podría tomar las iniciales). Apple prefigura clases con NS y dice que este prefijo está reservado solo para Apple.

Hasta ahora tan bien. Pero añadir de 2 a 4 letras a un nombre de clase delante es un espacio de nombres muy, muy limitado. Por ejemplo, MS o AI podrían tener significados completamente diferentes (AI podría ser Inteligencia Artificial, por ejemplo) y algún otro desarrollador podría decidir usarlos y crear una clase con el mismo nombre. Bang, colisión de espacio de nombres.

Bien, si esto es una colisión entre una de sus propias clases y una de un marco externo que está utilizando, puede fácilmente cambia el nombre de tu clase, no es gran cosa. Pero, ¿qué pasa si usas dos frameworks externos, ambos frameworks a los que no tienes la fuente y que no puedes cambiar? Su aplicación enlaza con ambos y obtiene conflictos de nombres. ¿Cómo resolverías esto? ¿Cuál es la mejor manera de trabajar alrededor de ellos de tal manera que todavía se puede utilizar ambas clases?

En C puede evitar esto al no enlazar directamente a la biblioteca, en lugar de cargar la biblioteca en runtime, usando dlopen (), luego encuentra el símbolo que estás buscando usando dlsym () y asígnalo a un símbolo global (que puedes nombrar como quieras) y luego accede a él a través de este símbolo global. Por ejemplo, si tiene un conflicto porque alguna biblioteca de C tiene una función llamada open(), podría definir una variable llamada MyOPEN y hacer que apunte a la función open() de la biblioteca, por lo tanto, cuando desee usar el sistema open (), solo use open() y cuando desee usar la otra, acceda a ella a través de el identificador MyOPEN.

¿Es posible algo similar en Objective-C y si no, hay alguna otra solución inteligente y complicada que pueda usar resolver conflictos de espacio de nombres? Alguna idea?


Actualización:

Solo para aclarar esto: las respuestas que sugieren cómo evitar colisiones de espacio de nombres por adelantado o cómo crear un mejor espacio de nombres son ciertamente bienvenidas; sin embargo, no las aceptaré como la respuesta ya que no resuelven mi problema. Tengo dos bibliotecas y su los nombres de las clases chocan. No puedo cambiarlos; no tengo la fuente de ninguno. La colisión ya está ahí y los consejos sobre cómo podría haberse evitado por adelantado ya no ayudarán. Puedo enviarlos a los desarrolladores de estos frameworks y espero que elijan un mejor espacio de nombres en el futuro, pero por el momento estoy buscando una solución para trabajar con los frameworks ahora mismo dentro de una sola aplicación. ¿Alguna solución que lo haga posible?

Author: Jonas, 2008-10-07

13 answers

Si no necesita usar clases de ambos frameworks al mismo tiempo, y se dirige a plataformas que admiten la descarga de NSBundle (OS X 10.4 o posterior, sin soporte de GNUstep), y el rendimiento realmente no es un problema para usted, creo que podría cargar un framework cada vez que necesite usar una clase de él, y luego descargarlo y cargar el otro cuando necesite usar el otro framework.

Mi idea inicial era usar NSBundle para cargar uno de los frameworks, luego copiar o cambie el nombre de las clases dentro de ese marco y, a continuación, cargue el otro marco. Hay dos problemas con esto. Primero, no pude encontrar una función para copiar los datos apuntados para renombrar o copiar una clase, y cualquier otra clase en ese primer marco que haga referencia a la clase renombrada ahora haría referencia a la clase del otro marco.

No necesitarías copiar o renombrar una clase si hubiera una manera de copiar los datos apuntados por un IMP. Puede crear una nueva clase y luego copiar ivars, métodos, propiedades y categorías. Mucho más trabajo, pero es posible. Sin embargo, todavía tendría un problema con las otras clases en el marco haciendo referencia a la clase incorrecta.

EDIT: La diferencia fundamental entre los tiempos de ejecución de C y Objective-C es, según entiendo, que cuando se cargan bibliotecas, las funciones en esas bibliotecas contienen punteros a cualquier símbolo al que hagan referencia, mientras que en Objective-C, contienen representaciones de cadena de los nombres de estos símbolos. Por lo tanto, en su ejemplo, puede usar dlsym para obtener la dirección del símbolo en la memoria y adjuntarla a otro símbolo. El otro código en la biblioteca todavía funciona porque no está cambiando la dirección del símbolo original. Objective-C utiliza una tabla de búsqueda para asignar nombres de clases a direcciones, y es una asignación 1-1, por lo que no puede tener dos clases con el mismo nombre. Por lo tanto, para cargar ambas clases, uno de ellos debe tener su nombre cambiado. Sin embargo, cuando otras clases necesitan acceder a una de las clases con nombre, le pedirán a la tabla de búsqueda su dirección, y la tabla de búsqueda nunca devolverá la dirección de la clase renombrada dado el nombre de la clase original.

 47
Author: Michael Buckley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-08 05:25:08

Prefijar sus clases con un prefijo único es fundamentalmente la única opción, pero hay varias maneras de hacer que esto sea menos oneroso y feo. Hay una larga discusión de las opciones aquí. Mi favorita es la directiva del compilador de Objective-C @compatibility_alias (descrita aquí). Puede usar @compatibility_alias para "renombrar" una clase, lo que le permite nombrar su clase usando FQDN o algún prefijo:

@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 una estrategia completa, puedes prefijar todas tus clases con un único prefijo como el FQDN y luego crear un encabezado con todos los @compatibility_alias (me imagino que podría generar automáticamente dicho encabezado).

La desventaja de prefijar así es que tiene que introducir el verdadero nombre de clase (por ejemplo, COM_WHATEVER_ClassName arriba) en cualquier cosa que necesite el nombre de clase de una cadena además del compilador. Notablemente, @compatibility_alias es una directiva de compilador, no una función de tiempo de ejecución, por lo que NSClassFromString(ClassName) fallará (devuelve nil) you tendrá que usar NSClassFromString(COM_WHATERVER_ClassName). Puede usar ibtool a través de la fase de compilación para modificar Interfaz Builder nib / xib para que no tenga que escribir el COM_WHATEVER_ completo... en Interface Builder.

Advertencia final: debido a que esta es una directiva de compilador (y una oscura), puede que no sea portable entre compiladores. En particular, no se si funciona con el frontend Clang del proyecto LLVM, aunque debería funcionar con LLVM-GCC (LLVM usando el frontend GCC).

 92
Author: Barry Wark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-24 16:06:15

Varias personas ya han compartido algún código complicado e inteligente que podría ayudar a resolver el problema. Algunas de las sugerencias pueden funcionar, pero todas son menos que ideales, y algunas de ellas son francamente desagradables de implementar. (A veces los ataques feos son inevitables, pero trato de evitarlos siempre que puedo.) Desde un punto de vista práctico, aquí están mis sugerencias.

  1. En cualquier caso, informar a los desarrolladores de ambos marcos del conflicto, y dejar claro que su fracaso para evitar y / o lidiar con él le está causando problemas comerciales reales, que podrían traducirse en ingresos comerciales perdidos si no se resuelven. Enfatice que si bien resolver los conflictos existentes en una base por clase es una solución menos intrusiva, cambiar su prefijo por completo (o usar uno si no lo están actualmente, ¡y debería avergonzarlos!) es la mejor manera de asegurarse de que no van a ver el mismo problema de nuevo.
  2. Si los conflictos de nombres se limitan a un conjunto razonablemente pequeño de clases, vea si puede solucionarlos solo esas clases, especialmente si una de las clases en conflicto no está siendo utilizada por su código, directa o indirectamente. Si es así, consulte si el proveedor proporcionará una versión personalizada del marco que no incluya las clases en conflicto. Si no, sea franco sobre el hecho de que su inflexibilidad está reduciendo su ROI al usar su marco. No te sientas mal por ser agresivo dentro de lo razonable: el cliente siempre tiene la razón. ;-)
  3. Si un framework es más "prescindible", es posible que considere reemplazarlo con otro marco (o combinación de código), ya sea de terceros o de homebrew. (Este último es el peor de los casos indeseable, ya que sin duda incurrirá en costos comerciales adicionales, tanto para el desarrollo como para el mantenimiento.) Si lo hace, informe al proveedor de ese marco exactamente por qué decidió no usar su marco.
  4. Si ambos frameworks se consideran igualmente indispensables para su aplicación, explore maneras de factorizar el uso de uno de ellos a uno o más separados procesos, tal vez la comunicación a través de HACER como Louis Gerbarg sugirió. Dependiendo del grado de comunicación, esto puede no ser tan malo como cabría esperar. Varios programas (incluyendo QuickTime, creo) utilizan este enfoque para proporcionar una seguridad más granular proporcionada mediante el uso de Perfiles de caja de arena de cinturón de seguridad en Leopard, de modo que solo un subconjunto específico de su código se permite realizar operaciones críticas o sensibles. El rendimiento será una compensación, pero puede ser su único opción

Supongo que las tarifas de licencia, los términos y las duraciones pueden impedir la acción instantánea en cualquiera de estos puntos. Espero que puedas resolver el conflicto lo antes posible. ¡Buena suerte!

 12
Author: Quinn Taylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-16 17:43:34

Esto es bruto, pero podría usar objetos distribuidos para mantener una de las clases solo en una dirección de programa subordinada y RPC a ella. Eso se complicará si está pasando un montón de cosas de un lado a otro (y puede no ser posible si ambas clases están manipulando directamente las vistas, etc.).

Hay otras soluciones potenciales, pero muchas de ellas dependen de la situación exacta. En particular, ¿está utilizando los tiempos de ejecución modernos o heredados, arquitectura, 32 o 64 bits, a qué versiones del sistema operativo se dirige, está enlazando dinámicamente, enlazando estáticamente, o tiene una opción, y está potencialmente bien hacer algo que pueda requerir mantenimiento para nuevas actualizaciones de software.

Si estás realmente desesperado, lo que podrías hacer es:

  1. No enlazar directamente con una de las bibliotecas
  2. Implementar una versión alternativa de las rutinas de tiempo de ejecución objc que cambia el nombre en el tiempo de carga (checkout the objc4 proyecto, lo que necesita hacer exactamente depende de varias de las preguntas que hice anteriormente, pero debería ser posible sin importar cuáles sean las respuestas).
  3. Usa algo como mach_override para inyectar tu nueva implementación
  4. Cargue la nueva biblioteca usando métodos normales, pasará por la rutina del enlazador parcheado y cambiará su nombre de clase

Lo anterior va a ser bastante laborioso, y si necesita implementarlo contra múltiples archs y diferentes versiones de tiempo de ejecución será muy desagradable, pero definitivamente se puede hacer que funcione.

 8
Author: Louis Gerbarg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 00:00:52

¿Ha considerado usar las funciones de tiempo de ejecución (/usr/include/objc/runtime.h) para clonar una de las clases en conflicto a una clase que no colisione, y luego cargar el framework de la clase que colisione? (esto requeriría que los marcos colisionantes se cargaran en diferentes momentos para funcionar.)

Puede inspeccionar las clases ivars, métodos (con nombres y direcciones de implementación) y nombres con el tiempo de ejecución, y crear el suyo propio también dinámicamente para tener el mismo diseño de ivar, métodos nombres / direcciones de implementación, y solo difieren por nombre (para evitar la colisión)

 4
Author: xtophyr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-07 18:35:53

Las situaciones desesperadas requieren medidas desesperadas. ¿Ha considerado piratear el código objeto (o archivo de biblioteca) de una de las bibliotecas, cambiando el símbolo de colisión a un nombre alternativo - de la misma longitud pero una ortografía diferente (pero, recomendación, la misma longitud del nombre)? Inherentemente desagradable.

No está claro si su código está llamando directamente a las dos funciones con el mismo nombre pero diferentes implementaciones o si el conflicto es indirecto (tampoco está claro si hace cualquier diferencia). Sin embargo, hay al menos una posibilidad externa de que el cambio de nombre funcione. También podría ser una idea minimizar la diferencia en la ortografía, de modo que si los símbolos están en un orden ordenado en una tabla, el cambio de nombre no mueva las cosas fuera de orden. Cosas como la búsqueda binaria se molestan si la matriz que están buscando no está en orden ordenado como se esperaba.

 3
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-27 07:49:06

Parece que el problema es que no puede hacer referencia a los archivos de encabezados de ambos sistemas en la misma unidad de traducción (archivo fuente). Si crea wrappers de objective-c alrededor de las bibliotecas (haciéndolas más usables en el proceso), y solo #incluye los encabezados de cada biblioteca en la implementación de las clases wrapper, eso separaría efectivamente las colisiones de nombres.

No tengo suficiente experiencia con esto en objective-c (recién comenzando), pero creo que eso es lo que lo haría en C.

 1
Author: chrish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-04 03:54:42

@compatibility_alias será capaz de resolver conflictos de espacio de nombres de clase, por ejemplo,

@compatibility_alias NewAliasClass OriginalClass;

Sin embargo, esto no resolverá ninguna de las colisiones enum, typedefs o protocol namespace. Además, no se juega bien con @class decl hacia adelante de la clase original. Dado que la mayoría de los frameworks vienen con estas cosas que no son de clase como typedefs, es probable que no pueda solucionar el problema del espacio de nombres con solo compatibility_alias.

Miré un problema similar a tuyo, pero yo tenía acceso a la fuente y estaba construyendo los frameworks. La mejor solución que encontré para esto fue usar @compatibility_alias condicionalmente con #defines para soportar los enums/typedefs/protocols/etc. Puede hacer esto condicionalmente en la unidad de compilación para el encabezado en cuestión para minimizar el riesgo de expandir cosas en el otro marco de colisión.

 1
Author: Michael Chinen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-26 19:04:26

Prefijar los archivos es la solución más simple que conozco. Cocoadev tiene una página de espacio de nombres que es un esfuerzo de la comunidad para evitar colisiones de espacio de nombres. Siéntase libre de agregar el suyo propio a esta lista, creo que para eso es.

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

 0
Author: Ryan Townshend,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-07 14:14:04

Si tiene una colisión, le sugeriría que piense detenidamente cómo podría refactorizar uno de los frameworks de su aplicación. Tener una colisión sugiere que los dos están haciendo cosas similares, y es probable que pueda moverse usando un marco adicional simplemente refactorizando su aplicación. Esto no solo resolvería su problema de espacio de nombres, sino que haría que su código sea más robusto, más fácil de mantener y más eficiente.

Sobre una solución más técnica, si estuviera en tu posición esta sería mi elección.

 0
Author: Allyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-05 21:20:00

Si la colisión es solo en el nivel de enlace estático, puede elegir qué biblioteca se utiliza para resolver símbolos:

cc foo.o -ldog bar.o -lcat

Si foo.o y bar.o referencia el símbolo rat entonces libdog resolverá foo.o's rat y libcat resolverá bar.o's rat.

 0
Author: wcochran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-20 03:27:45

Solo una idea.. no probado o probado y podría ser manera de la marca pero en ha considerado escribir un adaptador para la clase que utiliza desde el más simple de los frameworks.. o al menos sus interfaces?

Si tuviera que escribir un wrapper alrededor del más simple de los frameworks (o el que tiene las interfaces a las que menos accede) no sería posible compilar ese wrapper en una biblioteca. Dado que la biblioteca está precompilada y solo sus encabezados necesitan ser distribuidos, ocultar efectivamente el marco subyacente y sería libre de combinarlo con el segundo marco con choques.

Aprecio, por supuesto, que es probable que haya momentos en los que necesite usar clases de ambos frameworks al mismo tiempo, sin embargo, podría proporcionar fábricas para más adaptadores de clase de ese framework. En la parte posterior de ese punto, supongo que necesitaría un poco de refactorización para extraer las interfaces que está utilizando de ambos marcos, lo que debería proporcionar una buena punto de partida para que usted pueda construir su envoltura.

Puede construir sobre la biblioteca cuando necesite más funcionalidad de la biblioteca empaquetada, y simplemente recompilar cuando cambie.

De nuevo, de ninguna manera probado, pero se sentía como añadir una perspectiva. espero que ayude :)

 0
Author: mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-31 16:01:52

Si tiene dos frameworks que tienen el mismo nombre de función, puede intentar cargar dinámicamente los frameworks. No será elegante, pero es posible. Cómo hacerlo con las clases de Objective-C, no lo sé. Supongo que la clase NSBundle tendrá métodos que cargarán una clase específica.

 -1
Author: MaddTheSane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-18 00:54:49