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

174

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, puede usar "MS"; y así. A veces las iniciales se refieren al proyecto, por ejemplo, las clases de prefijos de Adium con "AI" (ya que no hay ninguna compañía detrás de eso, podría tomar las iniciales). Apple clasifica los prefijos 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 en 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 espacio de nombres.

De acuerdo, si se trata de una colisión entre una de sus propias clases y una de un marco externo que está utilizando, puede cambiar fácilmente el nombre de su clase, no es gran cosa. Pero, ¿qué sucede si usa dos marcos externos, ambos marcos para los que 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 resolver esto? ¿Cuál es la mejor manera de evitarlos de tal manera que aún pueda usar ambas clases?

En C puede solucionar estos problemas al no vincular directamente a la biblioteca, en su lugar, cargar la biblioteca en tiempo de ejecución, usar dlopen (), luego encontrar el símbolo que está buscando usando dlsym () y asignarlo a un símbolo global (que usted puede nombrar de la forma que 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, por lo tanto, cuando desee usar el sistema open () , solo usa open () y cuando desea usar el otro, accede a él a través del identificador myOpen.

¿Es posible algo similar en Objective-C? Si no, ¿hay alguna otra solución inteligente y difícil que pueda usar para resolver conflictos de espacio de nombres? ¿Algunas ideas?


Actualizar:

Solo para aclarar esto: las respuestas que sugieren cómo evitar las colisiones del espacio de nombres por adelantado o cómo crear un mejor espacio de nombres son ciertamente bienvenidas; sin embargo, no los aceptaré como respuesta ya que no resuelven mi problema. Tengo dos bibliotecas y sus nombres de clase chocan. No puedo cambiarlos. No tengo la fuente de ninguno de los dos. La colisión ya está allí y los consejos sobre cómo podría haberse evitado de antemano ya no ayudarán. Puedo reenviarlos 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 en este momento dentro de una sola aplicación. ¿Alguna solución para hacer esto posible?

Mecki
fuente
77
Tiene una buena pregunta (qué hacer si necesita dos marcos que tienen una colisión de nombres) pero está oculto en el texto. Revise para aclararlo y evitará respuestas simplistas como la que tiene ahora.
benzado
44
Esta es mi mayor queja con el diseño actual del lenguaje Objective-C. Mira las respuestas a continuación; aquellos que realmente abordan la pregunta (descarga de NSBundle, uso de DO, etc.) son hacks horribles que simplemente no deberían ser necesarios para algo tan trivial como evitar un conflicto de espacio de nombres.
erikprice 01 de
@erikprice: Amén. Estoy aprendiendo obj-c, y toqué este mismo problema. Vine aquí buscando una solución simple ... cojo.
Dave Mateer
1
Para el registro, técnicamente tanto C como Objective-C brindan soporte para múltiples espacios de nombres, aunque no es exactamente lo que está buscando el OP. Ver objectivistc.tumblr.com/post/3340816080/…
Hmm, no lo sabía. ¿Una decisión de diseño terrible, no?
Nico

Respuestas:

47

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 que apuntaban a 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 desde el otro marco.

No necesitaría copiar o cambiar el nombre de una clase si hubiera una forma de copiar los datos señalados por un IMP. Puede crear una nueva clase y luego copiar sobre 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 C y Objective-C es, según tengo entendido, cuando las bibliotecas se cargan, las funciones en esas bibliotecas contienen punteros a los símbolos a los que hacen referencia, mientras que en Objective-C, contienen representaciones de cadena de 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 adjuntarlo a otro símbolo. El otro código en la biblioteca aún 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, una de ellas debe tener su nombre cambiado. Sin embargo, cuando otras clases necesitan acceder a una de las clases con ese nombre,

Michael Buckley
fuente
55
Creo que la descarga de paquetes no fue compatible hasta 10.5 o más tarde.
Quinn Taylor
93

Prefijar sus clases con un prefijo único es fundamentalmente la única opción, pero hay varias formas de hacerlo menos oneroso y feo. Hay una larga discusión de opciones aquí . Mi favorita es la @compatibility_aliasdirectiva del compilador Objective-C (descrita aquí ). Puede usar @compatibility_aliaspara "cambiar el nombre" de una clase, lo que le permite nombrar su clase utilizando 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, puede prefijar todas sus clases con un prefijo único como el FQDN y luego crear un encabezado con todos los @compatibility_alias (imagino que podría generar automáticamente dicho encabezado).

La desventaja de los prefijos como este es que debe ingresar el nombre de clase verdadero (por ejemplo, COM_WHATEVER_ClassNamearriba) en cualquier cosa que necesite el nombre de 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 NSClassFromString(ClassName)lo que fallará (volverá nil): tendrá que usarla NSClassFromString(COM_WHATERVER_ClassName). Puedes usaribtool través de la fase de compilación para modificar los nombres de clase en un nib / xib de Interface Builder para que no tenga que escribir COM_WHATEVER _... completo en Interface Builder.

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

Barry Wark
fuente
44
Voto a favor del alias de compatibilidad, ¡no sabía sobre eso! Gracias. Pero no resuelve mi problema. No tengo el control para cambiar ningún prefijo de ninguno de los marcos que estoy usando, solo los tengo en forma binaria y colisionan. ¿Qué debo hacer al respecto?
Mecki
¿En qué archivo (.h o .m) debe ir la línea @compatibility_alias?
Alex Basson
1
@Alex_Basson @compatibility_alias probablemente debería ir en el encabezado para que sea visible donde sea que esté visible la declaración de @interface.
Barry Wark
Me pregunto si puede usar @compatibility_alias con nombres de protocolos, o definiciones de typedef, o cualquier otra cosa.
Ali
Buenos pensamientos aquí. ¿Se podría utilizar el método swizzling para evitar que NSClassFromString (ClassName) devuelva nil para las clases con alias? Probablemente sea difícil encontrar todos los métodos que tomaron un nombre de clase.
Dickey Singh el
12

Varias personas ya han compartido un 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 son francamente desagradables de implementar. (A veces, los trucos 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, informe a los desarrolladores de ambos marcos del conflicto y deje en claro que si no lo evita y / o trata, le está causando problemas comerciales reales, lo que podría traducirse en ingresos comerciales perdidos si no se resuelve. Haga hincapié en que, si bien la resolución de conflictos existentes por clase es una solución menos intrusiva, cambiar su prefijo por completo (o usar uno si no lo están actualmente, y ¡qué vergüenza!) Es la mejor manera de asegurarse de que no Ver el mismo problema de nuevo.
  2. Si los conflictos de nombres se limitan a un conjunto de clases razonablemente pequeño, vea si puede solucionar solo esas clases, especialmente si su código no está utilizando 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 su ROI al usar su marco. No se sienta mal por ser agresivo dentro de lo razonable: el cliente siempre tiene la razón. ;-)
  3. Si un marco es más "prescindible", puede considerar reemplazarlo por otro marco (o combinación de código), ya sea de terceros o homebrew. (Este último es el peor de los casos indeseables, 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 marcos se consideran igualmente indispensables para su aplicación, explore formas de factorizar 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, esto puede no ser tan malo como podría esperarse. Varios programas (incluido QuickTime, creo) utilizan este enfoque para proporcionar una seguridad más granular proporcionada al usar los perfiles de sandbox de zona de seguridad en Leopard , de modo que solo un subconjunto específico de su código puede realizar operaciones críticas o confidenciales. El rendimiento será una compensación, pero puede ser su única opción.

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

Quinn Taylor
fuente
8

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

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

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

  1. No enlazar 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 una serie de preguntas que hice anteriormente, pero debería ser posible sin importar cuáles sean las respuestas) )
  3. Use algo como mach_override para inyectar su nueva implementación
  4. Cargue la nueva biblioteca utilizando métodos normales, pasará por la rutina del enlazador parcheado y cambiará su className

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

Louis Gerbarg
fuente
4

¿Ha considerado usar las funciones de tiempo de ejecución (/usr/include/objc/runtime.h) para clonar una de las clases en conflicto en una clase que no colisiona y luego cargar el marco de clase de colisión? (esto requeriría que los marcos de colisión se carguen 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 los suyos también dinámicamente para tener el mismo diseño ivar, nombres de métodos / direcciones de implementación, y solo diferir por nombre (para evitar el colisión)

xtophyr
fuente
3

Situaciones desesperadas requieren medidas desesperadas. ¿Ha considerado hackear 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 de nombre)? Inherentemente desagradable.

No está claro si su código está llamando directamente a las dos funciones con el mismo nombre pero implementaciones diferentes o si el conflicto es indirecto (ni está claro si hace alguna 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 ordenados en una tabla, el cambio de nombre no cambia las cosas. Cosas como la búsqueda binaria se alteran si la matriz que están buscando no está ordenada como se esperaba.

Jonathan Leffler
fuente
Cambiar la biblioteca en el disco está fuera de discusión, porque la licencia no lo permitirá. Sería posible cambiar los símbolos en la memoria, pero no veo cómo se podría hacer esto (cargando la lib en la memoria, modificándola y luego pasándola al enlazador dinámico ... no veo ningún método para eso).
Mecki
3
OK: es hora de decidir cuál de las dos bibliotecas es menos importante y encontrar un reemplazo. Y explique a la persona por la que paga pero que está abandonando que la razón por la que está cambiando es por esta colisión y pueden retener su negocio solucionando su problema.
Jonathan Leffler
2

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

@compatibility_alias NewAliasClass OriginalClass;

Sin embargo, esto no resolverá ninguna de las enumeraciones, definiciones de tipo o colisiones de espacio de nombres de protocolo . Además, no juega bien con @classdecls hacia adelante de la clase original. Dado que la mayoría de los frameworks vendrán con estas cosas que no son de clase, como typedefs, es probable que no pueda solucionar el problema de espacio de nombres solo con compatibilidad_alias.

Miré un problema similar al tuyo , pero tenía acceso a la fuente y estaba construyendo los marcos. La mejor solución que encontré para esto fue usar @compatibility_aliascondicionalmente con #defines para admitir las enumeraciones / typedefs / protocolos / etc. Puede hacer esto condicionalmente en la unidad de compilación del encabezado en cuestión para minimizar el riesgo de expandir cosas en el otro marco de colisión.

Michael Chinen
fuente
1

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

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

chrish
fuente
1
Pero, ¿no se toparía con colisiones si intentara incluir ambos encabezados de envoltura en el mismo archivo (ya que cada uno necesitaría incluir los encabezados de sus respectivos marcos)?
Wilco
0

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 espacios 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

Ryan Townshend
fuente
¿Cómo ayuda el prefijo de los archivos si los objetos definidos dentro de los archivos causan el problema? Seguramente puedo manipular los archivos h, pero luego el enlazador ya no encontrará los objetos cuando me
guste en
Creo que esto sería una mejor práctica que una solución al problema inicial.
Ali
Esto no responde en absoluto a la pregunta
Madbreaks
0

Si tiene una colisión, le sugiero que piense detenidamente cómo podría refactorizar uno de los marcos de su aplicación. Tener una colisión sugiere que los dos están haciendo cosas similares a las suyas, 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 su posición, esta sería mi elección.

Allyn
fuente
0

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.oy bar.otanto el símbolo de referencia rata continuación, libdogse resolverá foo.o's raty libcatresolverá bar.o' s rat.

wcochran
fuente
0

Solo un pensamiento ... no probado o comprobado y podría ser la marca, pero ¿ha considerado escribir un adaptador para la clase que utiliza desde el marco más simple ... o al menos sus interfaces?

Si tuviera que escribir un contenedor alrededor del marco más simple (o de las interfaces a las que accede menos), no sería posible compilar ese contenedor en una biblioteca. Dado que la biblioteca está precompilada y solo su necesario distribuir encabezados, estaría ocultando efectivamente el marco subyacente y sería libre de combinarlo con el segundo marco con el choque.

Aprecio, por supuesto, que es probable que haya ocasiones en las que necesite usar las clases de ambos marcos al mismo tiempo, sin embargo, podría proporcionar fábricas para otros adaptadores de clase de ese marco. 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 un buen punto de partida para construir su contenedor.

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

Una vez más, de ninguna manera probado, pero tenía ganas de agregar una perspectiva. Espero eso ayude :)

marca
fuente
-1

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

MaddTheSane
fuente