objective-c - tutorial - objective c vs swift
¿Cuál es la mejor manera de resolver una colisión de espacio de nombres de Objective-C? (13)
¿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 está en colisión y luego cargar el marco de clase colisionante? (esto requeriría que los marcos colisionantes se carguen en diferentes momentos para que funcionen).
Puede inspeccionar las clases, 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 de ivar, nombres de métodos / direcciones de implementación, y solo diferir por nombre (para evitar el colisión)
Objective-C no tiene espacios de nombres; es muy parecido a C, todo está dentro de un espacio de nombre global. La práctica común es prefijar clases con iniciales, por ejemplo, si está trabajando en IBM, podría prefijarlas con "IBM"; si trabajas para Microsoft, podrías usar "MS"; y así. Algunas veces las iniciales se refieren al proyecto, por ejemplo, Adium prefija las clases con "AI" (ya que no hay compañía detrás de la que pueda tomar las iniciales). Apple prefija 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 a un nombre de 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 igualmente nombrada. Bang , colisión del espacio de nombres.
De acuerdo, si esto es una colisión entre una de tus 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é sucede si utiliza dos marcos externos, ambos marcos a los que no tiene acceso y que no puede cambiar? Su aplicación se vincula con ambos y usted obtiene conflictos de nombre. ¿Cómo resolverías esto? ¿Cuál es la mejor manera de evitarlos de tal manera que todavía puede usar ambas clases?
En C puedes evitar esto al no vincular directamente a la biblioteca, en su lugar cargas la biblioteca en tiempo de ejecución, usando dlopen (), luego encuentras el símbolo que estás buscando usando dlsym () y lo asignas a un símbolo global (que tú puede nombrar de la manera que desee) y luego acceder 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 quiera usar el sistema, abra () , solo usa open () y cuando quiere usar el otro, accede a través del identificador myOpen.
¿Es posible algo similar en Objective-C y, de no ser así, existe alguna otra solución ingeniosa y complicada que pueda usar para resolver conflictos de espacios de nombres? ¿Algunas ideas?
Actualizar:
Solo para aclarar esto: las respuestas que sugieren cómo evitar las colisiones de espacios de nombres de antemano o cómo crear un mejor espacio de nombres son ciertamente bienvenidas; sin embargo, no los aceptaré como la respuesta ya que no resuelven mi problema. Tengo dos bibliotecas y sus nombres de clase colisionan. No puedo cambiarlos; No tengo la fuente de ninguno. La colisión ya está allí y los consejos sobre cómo podría haberse evitado por adelantado ya no servirán de nada. 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?
Esto es asqueroso, pero podría usar objetos distribuidos para mantener una de las clases solo en una dirección de programas subordinados y RPC. Eso se complicará si estás pasando una tonelada de cosas de ida y vuelta (y puede que no sea posible si ambas clases manipulan directamente las vistas, etc.).
Existen 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, es una arquitectura simple o de 32 o 64 bits, a qué lanzamientos de sistema operativo se dirige, 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ás realmente desesperado, lo que podrías hacer es:
- No enlazar directamente con una de las bibliotecas
- Implementar una versión alternativa de las rutinas de ejecución de objc que cambie el nombre en el momento de la carga ( objc4 proyecto objc4 , qué necesita hacer exactamente depende de varias de las preguntas que hice anteriormente, pero debería ser posible sin importar cuáles sean las respuestas )
- Use algo como mach_override para inyectar su nueva implementación
- Cargue la nueva biblioteca con los métodos normales, revisará la rutina del enlazador y obtendrá su className cambiado
Lo anterior va a ser bastante laborioso, y si necesita implementarlo contra múltiples archios y diferentes versiones de tiempo de ejecución, será muy desagradable, pero definitivamente puede funcionar.
Las situaciones desesperadas requieren medidas desesperadas. ¿Ha pensado en piratear el código del objeto (o el archivo de la biblioteca) de una de las bibliotecas, cambiando el símbolo que colisiona por otro alternativo, de la misma longitud pero diferente ortografía (pero, recomendación, la misma longitud de nombre)? Inherentemente desagradable.
No está claro si su código llama directamente a las dos funciones con el mismo nombre pero diferentes implementaciones o si el conflicto es indirecto (tampoco está claro si hace alguna diferencia). Sin embargo, existe al menos una posibilidad de que el cambio de nombre funcione. También podría ser una idea para 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 mueve las cosas fuera de orden. Cosas como la búsqueda binaria se alteran si la matriz que están buscando no está ordenada como se esperaba.
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 contenedores 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 contenedoras, eso separaría eficazmente las colisiones de nombres.
No tengo suficiente experiencia con esto en Objective-c (solo comencé), pero creo que eso es lo que haría en C.
Prefijo de 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 a esta lista, creo que para eso sirve.
Si la colisión solo se produce en el nivel de enlace estático, puede elegir qué biblioteca usar para resolver los símbolos:
cc foo.o -ldog bar.o -lcat
Si foo.o
y bar.o
referencia al símbolo rat
, libdog
resolverá la rat
libcat
y libcat
resolverá la rat
bar.o
Si no necesita usar clases de ambos frameworks al mismo tiempo, y está apuntando 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 puede cargar un marco cada vez que necesita usar una clase del mismo, y luego descargarlo y cargar el otro cuando necesite usar el otro marco.
Mi idea inicial fue utilizar NSBundle para cargar uno de los frameworks, luego copiar o renombrar las clases dentro de ese framework, y luego cargar el otro framework. 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 sería necesario copiar o cambiar el nombre de una clase si hubiera una forma de copiar los datos apuntados 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 hace 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 hagan referencia, mientras que en Objective-C contienen representaciones de cadenas del nombres de los símbolos thsoe. 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 todavía funciona porque no está cambiando la dirección del símbolo original. Objective-C usa una tabla de búsqueda para asignar nombres de clase a direcciones, y es una asignación de 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 ese nombre, le pedirán su dirección a la tabla de búsqueda, y la tabla de búsqueda nunca devolverá la dirección de la clase renombrada dado el nombre de la clase original.
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 clases Objective-C, no sé. Supongo que la clase NSBundle
tendrá métodos que cargarán una clase específica.
Si tiene una colisión, le sugiero que reflexione sobre 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, y es probable que puedas utilizar un marco adicional simplemente refacturando tu 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.
Solo un pensamiento ... no probado o probado, y podría ser una buena señal, pero ¿ha considerado escribir un adaptador para la clase que utiliza desde el más simple de los marcos ... o al menos sus interfaces?
Si tuvieras que escribir un envoltorio alrededor de los frameworks más simples (o el de las interfaces a las que accedes menos) no sería posible compilar ese wrapper en una biblioteca. Dado que la biblioteca está precompilada y solo se deben distribuir sus encabezados, estarías efectivamente ocultando el marco subyacente y sería libre de combinarlo con el segundo marco con enfrentamientos.
Aprecio por supuesto que es probable que haya ocasiones en las que necesite usar 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 desde ambos marcos, lo que debería proporcionar un buen punto de partida para que pueda compilar su contenedor.
Puede construir sobre la biblioteca como usted y cuando necesite más funcionalidades de la biblioteca empaquetada, y simplemente recompilar cuando cambie.
Una vez más, de ninguna manera probado, pero sentí la necesidad de agregar una perspectiva. Espero eso ayude :)
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 hacks feos son inevitables, pero trato de evitarlos siempre que puedo.) Desde un punto de vista práctico, aquí están mis sugerencias.
- En cualquier caso, informe a los desarrolladores de ambos marcos del conflicto y deje en claro que si no lo evitan y / o lo enfrentan, le están causando problemas comerciales reales, que podrían traducirse en ingresos comerciales perdidos si no se resuelven. Haga hincapié en 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, y ¡qué vergüenza para ellos!) Es la mejor manera de asegurarse de que no lo hagan ve el mismo problema otra vez
- Si los conflictos de nomenclatura están limitados a un conjunto razonablemente pequeño de clases, vea si puede trabajar solo con esas clases, especialmente si el código no está utilizando una de las clases en conflicto, directa o indirectamente. De ser así, compruebe si el proveedor proporcionará una versión personalizada del marco que no incluya las clases en conflicto. Si no, sea sincero sobre el hecho de que su inflexibilidad está reduciendo su ROI de usar su marco. No se sienta mal por ser insistente dentro de lo razonable: el cliente siempre tiene la razón. ;-)
- Si un marco es más "prescindible", puede considerar reemplazarlo con otro marco (o combinación de código), ya sea de terceros o homebrew. (Este último es el peor caso 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 utilizar su marco.
- Si ambos marcos se consideran igualmente indispensables para su aplicación, explore maneras 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 cabría esperar. Varios programas (incluido QuickTime, creo) utilizan este enfoque para proporcionar una seguridad más granular proporcionada mediante el uso de perfiles de recinto de seguridad de cinturón 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 acciones instantáneas en cualquiera de estos puntos. Con suerte, podrá resolver el conflicto lo antes posible. ¡Buena suerte!
@compatibility_alias
podrá resolver conflictos de espacio de nombres de clases, por ej.
@compatibility_alias NewAliasClass OriginalClass;
Sin embargo, esto no resolverá ninguna de las enums, typedefs o colisiones de espacio de nombres de protocolos . Además, no funciona bien con @class
forward decls de la clase original. Dado que la mayoría de los marcos vendrán con estos elementos que no pertenecen a la clase, como typedefs, es probable que no pueda solucionar el problema del espacio de nombres con solo aliar_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_alias
condicionalmente con #defines para soportar las enums / typedefs / protocols / 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 en conflicto.
Prefijo de 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 here . Mi favorito es la directiva del compilador @compatibility_alias
Objective-C (que se describe here ). Puede usar @compatibility_alias
para "cambiar el nombre" de una clase, lo que le permite nombrar su clase mediante FQDN o algún prefijo de este 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 una estrategia completa, puede prefijar todas sus clases con un prefijo único como el FQDN y luego crear un encabezado con todas las @compatibility_alias
(me imagino que podría generar automáticamente dicho encabezado).
La desventaja del prefijo es que debes ingresar el verdadero nombre de clase (por ejemplo, COM_WHATEVER_ClassName
) en cualquier cosa que necesite el nombre de clase de una cadena además del compilador. Notablemente, @compatibility_alias
es una directiva de compilación, no una función de tiempo de ejecución, por lo que NSClassFromString(ClassName)
no podrá (return nil
) - tendrá que usar NSClassFromString(COM_WHATERVER_ClassName)
. Puede usar ibtool
través de la fase de compilación para modificar los nombres de las clases en un Nib / xib de Interface Builder para que no tenga que escribir el COM_WHATEVER _... completo en el Interface Builder.
Advertencia final: debido a que esta es una directiva de compilación (y una oscura en ese sentido), puede no ser portátil entre los compiladores. En particular, no sé si funciona con el frontend Clang del proyecto LLVM, aunque debería funcionar con LLVM-GCC (LLVM usando el frontend GCC).