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

Resuelto Mecki asked hace 15 años • 13 respuestas

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 anteponer las clases a las iniciales; por ejemplo, si trabaja en IBM, puede anteponerles "IBM"; si trabaja para Microsoft, puede utilizar "MS"; etcétera. A veces, las iniciales se refieren al proyecto, por ejemplo, Adium antepone las clases con "AI" (ya que no hay ninguna empresa detrás de la cual se puedan tomar las iniciales). Apple antepone las clases con NS y dice que este prefijo está reservado solo para Apple.

Hasta ahora todo bien. Pero agregar de 2 a 4 letras al nombre de una clase al frente 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 espacios de nombres.

Bien, si se trata de una colisión entre una de tus propias clases y una de un marco externo que estás utilizando, puedes cambiar fácilmente el nombre de tu clase, no es gran cosa. Pero, ¿qué pasa si usa dos marcos externos, ambos marcos para los cuales no tiene la fuente y que no puede cambiar? Su aplicación se vincula con ambos y obtiene conflictos de nombres. ¿Cómo harías para resolverlos? ¿Cuál es la mejor manera de solucionarlos de tal manera que aún puedas usar ambas clases?

En C, puede solucionar estos problemas al no vincularlos directamente a la biblioteca; en su lugar, carga la biblioteca en tiempo de ejecución usando dlopen(), luego busca el símbolo que está buscando usando dlsym() y lo asigna a un símbolo global (que puede nombrarlo como desee) y luego acceder a él a través de este símbolo global. Por ejemplo, si tiene un conflicto porque alguna biblioteca C tiene una función llamada open(), puede definir una variable llamada myOpen y hacer que apunte a la función open() de la biblioteca, así cuando quiera usar el sistema open() , solo usas open() y cuando quieras usar el otro, accedes a él a través del identificador myOpen.

¿Es posible algo similar en Objective-C y, si no, hay alguna otra solución inteligente y complicada que pueda utilizar para resolver conflictos de espacios de nombres? ¿Algunas ideas?


Actualizar:

Sólo para aclarar esto: las respuestas que sugieren cómo evitar colisiones de espacios de nombres de antemano o cómo crear un mejor espacio de nombres son ciertamente bienvenidas; sin embargo, no las aceptaré como respuesta ya que no resuelven mi problema. Tengo dos bibliotecas y los nombres de sus clases chocan. No puedo cambiarlos; No tengo la fuente de ninguno de los dos. La colisión ya está ahí y los consejos sobre cómo se podría haber evitado de antemano ya no sirven de nada. Puedo remitirlos a los desarrolladores de estos marcos 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 marcos ahora mismo dentro de una sola aplicación. ¿Alguna solución para que esto sea posible?

Mecki avatar Oct 07 '08 20:10 Mecki
Aceptado

Prefijar tus clases con un prefijo único es fundamentalmente la única opción, pero hay varias maneras de hacer esto menos oneroso y feo. Aquí hay una larga discusión sobre las opciones . Mi favorita es la @compatibility_aliasdirectiva del compilador Objective-C (descrita aquí ). Puede utilizar @compatibility_aliaspara "cambiar el nombre" de una clase, lo que le permitirá nombrar su clase utilizando FQDN o algún prefijo similar:

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

La desventaja de prefijar de esta manera es que debe ingresar el nombre verdadero de la clase (por ejemplo, COM_WHATEVER_ClassNamearriba) en cualquier cosa que necesite el nombre de la clase de una cadena además del compilador. En particular, @compatibility_aliases una directiva del compilador, no una función de tiempo de ejecución, por lo que NSClassFromString(ClassName)fallará (volverá nil); tendrás que usar NSClassFromString(COM_WHATERVER_ClassName). Puede usarlo ibtoola través de la fase de compilación para modificar los nombres de clases en una punta/xib de Interface Builder para que no tenga que escribir el COM_WHATEVER_... completo en Interface Builder.

Advertencia final: debido a que se trata de una directiva de compilador (y además oscura), es posible que no sea portátil entre compiladores. En particular, no sé si funciona con la interfaz Clang del proyecto LLVM, aunque debería funcionar con LLVM-GCC (LLVM usando la interfaz GCC).

Barry Wark avatar Oct 07 '2008 22:10 Barry Wark

Si no necesita usar clases de ambos marcos al mismo tiempo y está apuntando a plataformas que admiten la descarga de NSBundle (OS X 10.4 o posterior, no es compatible con GNUStep), y el rendimiento realmente no es un problema para usted, creo que puede cargar un marco cada vez que necesite usar una clase de él, y luego descargarlo y cargar el otro cuando necesite usar el otro marco.

Mi idea inicial era usar NSBundle para cargar uno de los marcos, luego copiar o cambiar el nombre de las clases dentro de ese marco y luego cargar el otro marco. Hay dos problemas con esto. Primero, no pude encontrar una función para copiar los datos apuntados a cambiar el nombre 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ía copiar o cambiar el nombre de una clase si hubiera una manera de copiar los datos señalados 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, aún tendría un problema con las otras clases en el marco que hacen referencia a la clase incorrecta.

EDITAR: La diferencia fundamental entre los tiempos de ejecución de C y Objective-C es, según tengo entendido, cuando se cargan las bibliotecas, las funciones en esas bibliotecas contienen punteros a cualquier símbolo al que hacen referencia, mientras que en Objective-C, contienen representaciones de cadenas del nombres de esos 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 de la biblioteca todavía funciona porque no estás cambiando la dirección del símbolo original. Objective-C usa una tabla de búsqueda para asignar nombres de clases a direcciones, y es una asignación 1-1, por lo que no puedes tener dos clases con el mismo nombre. Así, para cargar ambas clases, es necesario cambiar el nombre de una de ellas. Sin embargo, cuando otras clases necesitan acceder a una de las clases con ese nombre, solicitarán su dirección a la tabla de búsqueda, y la tabla de búsqueda nunca devolverá la dirección de la clase cuyo nombre cambió dado el nombre de la clase original.

Michael Buckley avatar Oct 08 '2008 04:10 Michael Buckley

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 están lejos de ser ideales y algunas son francamente desagradables de implementar. (A veces los trucos desagradables son inevitables, pero trato de evitarlos siempre que puedo). Desde un punto de vista práctico, estas son mis sugerencias.

  1. En cualquier caso, informe a los desarrolladores de ambos marcos sobre el conflicto y deje claro que el hecho de no evitarlo o abordarlo le está causando problemas comerciales reales, que podrían traducirse en una pérdida de ingresos comerciales si no se resuelven. Enfatice que, si bien resolver los conflictos existentes por clase es una solución menos intrusiva, cambiar su prefijo por completo (o usar uno si no lo están actualmente, ¡qué vergüenza!) es la mejor manera de garantizar que no lo hagan. Vuelvo a ver el mismo problema.
  2. Si los conflictos de nombres se limitan a un conjunto razonablemente pequeño de clases, vea si puede solucionar solo esas clases, especialmente si su código no utiliza una de las clases en conflicto, directa o indirectamente. Si es así, vea si el proveedor proporcionará una versión personalizada del marco que no incluya las clases en conflicto. De lo contrario, sea franco sobre el hecho de que su inflexibilidad está reduciendo el retorno de su inversión al utilizar su marco. No se sienta mal por ser insistente dentro de lo razonable: el cliente siempre tiene la razón. ;-)
  3. Si un marco es más "prescindible", podría considerar reemplazarlo con otro marco (o combinación de código), ya sea de terceros o casero. (Este último es el peor caso indeseable, ya que ciertamente generará costos comerciales adicionales, tanto de desarrollo como de mantenimiento). Si lo hace, informe al proveedor de ese marco exactamente por qué decidió no utilizar su marco.
  4. Si ambos marcos se consideran igualmente indispensables para su aplicación, explore formas de descartar el uso de uno de ellos en uno o más procesos separados, tal vez comunicándose a través de DO como sugirió Louis Gerbarg. Dependiendo del grado de comunicación, es posible que esto no sea tan malo como cabría esperar. Varios programas (incluido QuickTime, creo) utilizan este enfoque para proporcionar una seguridad más granular proporcionada mediante el uso de perfiles Sandbox de Seatbelt en Leopard , de modo que solo un subconjunto específico de su código puede realizar operaciones críticas o sensibles. El rendimiento será una compensación, pero puede ser su única opción

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

Quinn Taylor avatar Jun 16 '2009 17:06 Quinn Taylor

Esto es asqueroso, pero podría usar objetos distribuidos para mantener una de las clases solo en la dirección de un programa subordinado y RPC. Eso se volverá complicado si pasas un montón de cosas de un lado a otro (y puede que no sea posible si ambas clases manipulan directamente las vistas, etc.).

Existen otras posibles soluciones, pero muchas de ellas dependen de la situación exacta. En particular, ¿está utilizando tiempos de ejecución modernos o heredados?, ¿tiene una arquitectura simple o gruesa, de 32 o 64 bits?, ¿a qué versiones del sistema operativo está apuntando?, ¿está enlazando dinámicamente, enlazando estáticamente o tiene una opción? Está 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 vincular directamente a una de las bibliotecas
  2. Implemente una versión alternativa de las rutinas de tiempo de ejecución de objc que cambie el nombre en el momento de la carga (consulte el proyecto objc4 , lo que debe hacer exactamente depende de varias de las preguntas que hice anteriormente, pero debería ser posible sin importar cuáles sean las respuestas). ).
  3. Utilice algo como mach_override para inyectar su nueva implementación
  4. Cargue la nueva biblioteca usando métodos normales, pasará por la rutina del vinculador parcheada y cambiará su nombre de clase.

Lo anterior requerirá bastante trabajo, y si necesita implementarlo en múltiples arcos y diferentes versiones de tiempo de ejecución, será muy desagradable, pero definitivamente se puede hacer que funcione.

Louis Gerbarg avatar Feb 28 '2009 00:02 Louis Gerbarg