Cómo resolver el conflicto de nombres de símbolos en un marco táctil de Cocoa

Desarrollé un marco de contacto de Cocoa y estoy teniendo problemas con las classs de frameworks estáticos de terceros que están incrustadas dentro de él.

El problema son las colisiones de símbolos cuando los proyectos de consumo utilizan mi marco y también importan el marco estático de terceros que usa mi marco.

Eventualmente quiero eliminar estas classs de mi marco ya que están en conflicto con las classs de proyecto de host (utilizan el mismo marco de trabajo de terceros) y, de alguna manera, le digo a mi marco que confíe en el marco principal de terceros del proyecto (les indicaré a los desarrolladores que importen el marco). O, alternativamente, agregaré un prefijo a estas classs para que cuando los proyectos de alojamiento incrusten mi marco y utilicen el mismo marco de trabajo de terceros que mi propio marco, no obtendrán colisiones de símbolos

¡Cualquier ayuda o dirección será bienvenida!

CocoaPods puede ayudarlo a resolver el problema con símbolos duplicates.
A continuación, proporcioné explicaciones detalladas sobre cómo hacer que sucediera:

Definiciones
Hagamos algunas definiciones para explicaciones más simples:
MyFramework : marco que está desarrollando.
MyApplication : aplicación que utiliza MyFramework .
OtherFramework : marco de trabajo de terceros que se utiliza en MyFramework y MyApplication .

Problema
Como entiendo, el problema es que Xcode no puede generar errores con "símbolos duplicates" en OtherFramework .

Solución
Estas son las condiciones que debe satisfacer para solucionar ese problema:

1) MyFramework debe referirse a OtherFramework by CocoaPods:

 // MyFramework Podfile use_frameworks! pod "OtherFramework" 

2) MyApplication debe referirse a OtherFramework by CocoaPods:

 // MyApplication Podfile use_frameworks! pod "OtherFramework" 

3) MyApplication puede usar cualquier mecanismo para referirse a MyFramework (por CocoaPods o por armazón de drag and drop para proyectar).

4) Otros frameworks deben buildse con CocoaPods.
Si aún no está construido con CocoaPods, puedes hacerlo tú mismo.
Para este objective, debe crear OtherFramework.podspec y, opcionalmente, enviarlo al repository privado de CocoaPods. No importa si tiene files de origen o solo el package OtherFramework.framework . Más detalles para build CocoaPod aquí .

TL; DR

Usando frameworks dynamics, no debería preocuparse demasiado por ello, ya que el linker usa un comportamiento pnetworkingeterminado sensible. Si realmente quiere hacer lo que pide, puede instruir al linker para que lo haga, a riesgo de fallar en time de ejecución. Mira hacia el final de la respuesta para get una explicación.

En general, con respecto a la vinculación estática

Esta es otra versión del clásico problema de "infierno de dependencia". Teóricamente, hay dos soluciones cuando se vinculan los files de object estáticamente:

  1. Indique su dependencia y permita que el usuario de su marco lo resuelva en su sistema de compilation. Algunas ideas:

    • Los sistemas externos como CocoaPods y Carthage lo ayudarán a desmerecer las restricciones en el sistema de compilation de su usuario (es decir, el usuario podría no usar CocoaPods).

    • Incluya la dependencia y los files de encabezado para ello, instruyendo a los usuarios que no usen la versión provista de esa dependencia. La desventaja es, por supuesto, que su usuario no puede cambiar la implementación en caso de que lo necesite.

    • (Tal vez el más fácil). Cree dos versiones de su marco, una con la biblioteca de dependencies no vinculada. El usuario puede elegir a quién usar su versión proporcionada o la suya propia. La desventaja es que no hay una buena forma de determinar si su versión será compatible con su código.

  2. Evita perder tu dependencia y encapsularla dentro de tu marco, a expensas de un tamaño de código mayor. Esta es generalmente mi ruta de preference en estos días ahora que el tamaño del código no es un problema real, incluso en dispositivos mobilees. Los methods incluyen:

    • Incluya la dependencia como files fuente en su proyecto. Use #define macros para cambiar el nombre de los símbolos (o usando solo símbolos static ).
    • Construya su dependencia desde la fuente, cambiando el nombre de los símbolos en un paso previo a la creación del código (o una sola).
    • Construir todo como eras, luego cambiar el nombre de los símbolos "internos" en la biblioteca construida. Esto podría causar errores, especialmente dado que swift / obj-c tienen aspectos dynamics. Mira esta pregunta , por ejemplo.

Esta publicación analiza algunos de los pros y los contras de las diferentes alternativas.

Al usar frameworks dynamics

Si está creando un marco dynamic, la mejor práctica es hacer "2." encima. Específicamente, vincular dinámicamente evitará el problema del símbolo duplicado, ya que su biblioteca puede vincular a su versión de la biblioteca de terceros independientemente de la biblioteca que use cualquier cliente.

Aquí hay un ejemplo simple (usar C para simplificar, pero debería aplicarse a Swift, ObjC, C ++ o cualquier cosa vinculada usando ld ):

Tenga en count también que supongo que su biblioteca de terceros está escrita en C / objC / C ++, ya que las classs Swift no pueden (AFAIK) vivir en bibliotecas estáticas.

myapp.c

 #include <stdio.h> void main() { printf("Hello from app\n"); third_party_func(); my_dylib_func(); } 

mylib.c

 #include <stdio.h> void my_dylib_func() { printf("Now in dylib\n"); third_party_func(); printf("Now exiting dylib\n"); } 

thirdparty_v1.c

 #include <stdio.h> void third_party_func() { printf("Third party func, v1\n"); } 

thirdparty_v2.c

 #include <stdio.h> void third_party_func() { printf("Third party func, v2\n"); } 

Ahora, comstackmos primero los files:

 $ clang -c *.c $ ls *.o myapp.o mylib.o thirdparty_v1.o thirdparty_v2.o 

A continuación, genere las bibliotecas estáticas y dinámicas

 $ ar -rcs libmystatic.a mylib.o thirdparty_v1.o $ ld -dylib mylib.o thirdparty_v1.o -lc -o libmydynamic.dylib $ ls libmy* | xargs file libmydynamic.dylib: Mach-O 64-bit dynamically linked shanetworking library x86_64 libmystatic.a: current ar archive random library 

Ahora, si comstackmos estáticamente utilizando la implementación (implícita) proporcionada:

 clang -omyapp myapp.o -L. -lmystatic thirdparty_v2.o && ./myapp Hello from app Third party func, v1 Now in dylib Third party func, v1 Now exiting dylib 

Ahora, esto fue bastante sorprendente para mí, ya que esperaba un error de "símbolo duplicado". Resulta que ld en OSX reemplaza silenciosamente los símbolos, haciendo que la aplicación del usuario reemplace los símbolos en mi biblioteca. El motivo está documentado en la página de manual de ld :

ld solo extraerá files .o de una biblioteca estática si es necesario para resolver alguna reference de símbolo

Esto correspondería al punto 1. anterior. (En una nota lateral, ejecutar el ejemplo anterior en Linux seguro da un error de "símbolo duplicado").

Ahora, vinculemos dinámicamente como en su ejemplo:

 clang -omyapp myapp.o -L. -lmydynamic thirdparty_v2.o && ./myapp Hello from app Third party func, v2 Now in dylib Third party func, v1 Now exiting dylib 

Como puede ver, su biblioteca dinámica ahora se refiere a su versión (v1) de la biblioteca estática, mientras que la propia aplicación usará la otra versión (v2). Esto es probablemente lo que desea, y este es el valor pnetworkingeterminado . La razón es, por supuesto, que ahora hay dos binarys, cada uno con su propio set de símbolos. Si inspeccionamos el .dylib , podemos ver que aún exporta la biblioteca de terceros:

 $ nm libmydynamic.dylib 0000000000000ec0 T _my_dylib_func U _printf 0000000000000f00 T _third_party_func U dyld_stub_binder 

Y seguro, si no vinlamos a la biblioteca estática en nuestra aplicación, el linker encontrará el símbolo en el .dylib :

 $ clang -omyapp myapp.o -L. -lmydynamic && ./myapp Hello from app Third party func, v1 Now in dylib Third party func, v1 Now exiting dylib 

Ahora, si no queremos exponer a la aplicación que usamos alguna biblioteca estática, podemos ocultarla al no exportar sus símbolos (esconderlo como si no permitiéramos que la aplicación lo hiciera de forma accidental, no lo ocultamos realmente):

 $ ld -dylib mylib.o -unexported_symbol '_third_party_*' thirdparty_v1.o -lc -o libmydynamic_no3p.dylib $ nm -A libmydyn* ... libmydynamic.dylib: 0000000000000f00 T _third_party_func libmydynamic_no3p.dylib: 0000000000000f00 t _third_party_func 

(Un T mayúscula significa que el símbolo es público, una minúscula t significa que el símbolo es privado).

Intentemos el último ejemplo nuevamente:

 $ clang -omyapp myapp.o -L. -lmydynamic_no3p && ./myapp Undefined symbols for architecture x86_64: "_third_party_func", referenced from: _main in myapp.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation) 

Ahora hemos ocultado con éxito nuestro marco estático de terceros a las aplicaciones de los clientes. Tenga en count que, nuevamente, normalmente no tendrá que preocuparse.

¿Qué pasa si realmente quieres 1. en frameworks dynamics

Por ejemplo, su lib puede necesitar la versión exacta de la biblioteca de terceros que proporciona su cliente.

También hay un indicador de linker para eso, por supuesto: -undefined dynamic_lookup .

 $ ld -dylib mylib.o -undefined dynamic -lc -o libmydynamic_undef.dylib $ clang -omyapp myapp.o -L. -lmydynamic_undef thirdparty_v2.o && ./myapp Hello from app Third party func, v2 Now in dylib Third party func, v2 Now exiting dylib 

La desventaja es, por supuesto, que fallaría en el time de ejecución si su cliente no puede include la biblioteca estática.

Siempre puede usar classs de un marco como este:

 import Alamofire let request = Alamofire.Request(...) 

Y si tiene una class de Request en su propio marco, puede usarla de la misma manera:

 import YourFramework let request = YourFramework.Request(...) 

Allí no habría ningún conflicto.

Tuve un problema similar antes, pero estaba usando el framework de terceros de Objective-C. El problema se resuelve mediante el uso del encabezado de puenteo para importar específicamente el drag and drop del marco al proyecto del consumidor, y dejar su marco tipo de cápsula. Es solo mi experiencia, por lo que podría no aplicarse a su caso, pero podría compartirla aquí por si acaso es útil.