¿Me puede guiar cómo vincular correctamente la biblioteca estática al proyecto de iPhone? Uso el proyecto de biblioteca estática agregado al proyecto de la aplicación como dependencia directa (destino -> general -> dependencias directas) y todo funciona bien, pero categorías. Una categoría definida en la biblioteca estática no funciona en la aplicación.
Entonces, mi pregunta es cómo agregar una biblioteca estática con algunas categorías en otro proyecto.
Y, en general, ¿cuál es la mejor práctica para usar en el código de proyecto de la aplicación de otros proyectos?
iphone
objective-c
static-libraries
categories
Vladimir
fuente
fuente
Respuestas:
Solución: a partir de Xcode 4.2, solo necesita ir a la aplicación que se vincula con la biblioteca (no la biblioteca en sí) y hacer clic en el proyecto en el Navegador de proyectos, hacer clic en el destino de su aplicación, luego crear configuraciones, luego buscar "Otro Banderas del vinculador ", haga clic en el botón + y agregue '-ObjC'. '-all_load' y '-force_load' ya no son necesarias.
Detalles: encontré algunas respuestas en varios foros, blogs y documentos de Apple. Ahora trato de hacer un breve resumen de mis búsquedas y experimentos.
El problema fue causado por (cita de Apple Technical Q&A QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html ):
Y su solución:
y también hay recomendaciones en las Preguntas frecuentes sobre desarrollo de iPhone:
y descripciones de banderas:
* podemos usar force_load para reducir el tamaño binario de la aplicación y evitar conflictos que all_load puede causar en algunos casos.
Sí, funciona con archivos * .a agregados al proyecto. Sin embargo, tuve problemas con el proyecto lib agregado como dependencia directa. Pero más tarde descubrí que era mi culpa: el proyecto de dependencia directa posiblemente no se agregó correctamente. Cuando lo elimino y lo agrego nuevamente con pasos:
después de eso todo funciona bien. La bandera "-ObjC" fue suficiente en mi caso.
También me interesó la idea del blog http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html . El autor dice que puede usar la categoría de lib sin establecer -all_load o -ObjC flag. Simplemente agrega a la categoría archivos h / m una interfaz / implementación de clase ficticia vacía para forzar que el enlazador use este archivo. Y sí, este truco hace el trabajo.
Pero el autor también dijo que incluso no instanciaba un objeto ficticio. Mm ... Como he encontrado, deberíamos llamar explícitamente algún código "real" del archivo de categoría. Entonces, al menos, la función de clase debería llamarse. E incluso no necesitamos clase falsa. La única función c hace lo mismo.
Entonces, si escribimos archivos lib como:
y si llamamos useMyLib (); en cualquier parte del proyecto de la aplicación, en cualquier clase podemos usar el método de categoría logSelf;
Y más blogs sobre el tema:
http://t-machine.org/index.php/2009/10/13/how-to-make-an-iphone-static-library-part-1/
http://blog.costan.us/2009/12/fat-iphone-static-libraries-device-and.html
fuente
La respuesta de Vladimir es bastante buena, sin embargo, me gustaría dar un poco más de conocimiento aquí. Tal vez algún día alguien encuentre mi respuesta y pueda encontrarla útil.
El compilador transforma los archivos fuente (.c, .cc, .cpp, .m) en archivos de objeto (.o). Hay un archivo de objeto por archivo fuente. Los archivos de objetos contienen símbolos, códigos y datos. Los archivos de objetos no son directamente utilizables por el sistema operativo.
Ahora, al construir una biblioteca dinámica (.dylib), un marco, un paquete cargable (.bundle) o un binario ejecutable, el enlazador enlaza estos archivos de objeto para producir algo que el sistema operativo considera "utilizable", por ejemplo, algo que puede cargar directamente a una dirección de memoria específica.
Sin embargo, cuando se construye una biblioteca estática, todos estos archivos de objeto simplemente se agregan a un archivo de archivo grande, de ahí la extensión de las bibliotecas estáticas (.a para archivo). Entonces, un archivo .a no es más que un archivo de objetos (.o). Piense en un archivo TAR o un archivo ZIP sin compresión. Es más fácil copiar un solo archivo .a que un montón de archivos .o (similar a Java, donde empaqueta los archivos .class en un archivo .jar para una fácil distribución).
Al vincular un binario a una biblioteca estática (= archivo), el vinculador obtendrá una tabla de todos los símbolos en el archivo y verificará a cuál de estos símbolos hacen referencia los binarios. Solo los archivos de objetos que contienen símbolos referenciados son realmente cargados por el enlazador y son considerados por el proceso de enlace. Por ejemplo, si su archivo tiene 50 archivos de objetos, pero solo 20 contienen símbolos utilizados por el binario, solo esos 20 son cargados por el enlazador, los otros 30 son completamente ignorados en el proceso de enlace.
Esto funciona bastante bien para el código C y C ++, ya que estos lenguajes intentan hacer todo lo posible en tiempo de compilación (aunque C ++ también tiene algunas características de tiempo de ejecución). Obj-C, sin embargo, es un tipo diferente de lenguaje. Obj-C depende en gran medida de las características de tiempo de ejecución y muchas características de Obj-C son en realidad características solo de tiempo de ejecución. Las clases Obj-C en realidad tienen símbolos comparables a las funciones C o variables C globales (al menos en el tiempo de ejecución Obj-C actual). Un vinculador puede ver si una clase está referenciada o no, por lo que puede determinar si una clase está en uso o no. Si utiliza una clase de un archivo de objeto en una biblioteca estática, el vinculador cargará este archivo de objeto porque el vinculador ve que se está utilizando un símbolo. Las categorías son una característica de tiempo de ejecución, las categorías no son símbolos como clases o funciones y eso también significa que un vinculador no puede determinar si una categoría está en uso o no.
Si el vinculador carga un archivo de objeto que contiene el código Obj-C, todas las partes Obj-C del mismo siempre forman parte de la etapa de vinculación. Por lo tanto, si se carga un archivo de objeto que contiene categorías porque cualquier símbolo del mismo se considera "en uso" (ya sea una clase, una función, una variable global), las categorías también se cargan y estarán disponibles en tiempo de ejecución . Sin embargo, si el archivo objeto en sí no está cargado, las categorías no estarán disponibles en tiempo de ejecución. Un archivo de objeto que contiene solo categorías nunca se carga porque no contiene símbolos que el vinculador alguna vez consideraría "en uso". Y este es todo el problema aquí.
Se han propuesto varias soluciones y ahora que sabe cómo funciona todo esto, echemos otro vistazo a la solución propuesta:
Una solución es agregar
-all_load
a la llamada del enlazador. ¿Qué hará realmente esa bandera de enlace? En realidad, le dice al enlazador lo siguiente: " Cargue todos los archivos de objetos de todos los archivos independientemente de si ve algún símbolo en uso o no ". Por supuesto, eso funcionará; pero también puede producir binarios bastante grandes.Otra solución es agregar
-force_load
a la llamada del vinculador, incluida la ruta al archivo. Este indicador funciona exactamente igual-all_load
, pero solo para el archivo especificado. Por supuesto, esto también funcionará.La solución más popular es agregar
-ObjC
a la llamada del enlazador. ¿Qué hará realmente esa bandera de enlace? Este indicador le dice al enlazador " Cargue todos los archivos de objetos de todos los archivos si ve que contienen algún código Obj-C ". Y "cualquier código Obj-C" incluye categorías. Esto también funcionará y no forzará la carga de archivos de objetos que no contengan código Obj-C (estos solo se cargan a pedido).Otra solución es la configuración de compilación Xcode bastante nueva
Perform Single-Object Prelink
. ¿Qué hará esta configuración? Si está habilitado, todos los archivos de objeto (recuerde, hay uno por archivo fuente) se fusionan en un solo archivo de objeto (que no es un enlace real, de ahí el nombre PreLink ) y este archivo de objeto único (a veces también llamado "objeto maestro" archivo ") se agrega al archivo. Si ahora se considera en uso cualquier símbolo del archivo de objeto maestro, se considera que está en uso todo el archivo de objeto maestro y, por lo tanto, todas las partes de Objective-C siempre se cargan. Y dado que las clases son símbolos normales, es suficiente usar una sola clase de una biblioteca estática para obtener todas las categorías.La solución final es el truco que Vladimir agregó al final de su respuesta. Coloque un " símbolo falso " en cualquier archivo fuente que declare solo categorías. Si desea utilizar cualquiera de las categorías en tiempo de ejecución, asegúrese de hacer referencia de alguna manera al símbolo falso en tiempo de compilación, ya que esto hace que el vinculador cargue el archivo de objeto y, por lo tanto, también todo el código Obj-C en él. Por ejemplo, podría ser una función con un cuerpo de función vacío (que no hará nada cuando se llame) o podría ser una variable global a la que se accede (por ejemplo, una función global
int
una vez leída o escrita, esto es suficiente). A diferencia de todas las otras soluciones anteriores, esta solución cambia el control sobre qué categorías están disponibles en tiempo de ejecución para el código compilado (si quiere que estén vinculadas y disponibles, accede al símbolo; de lo contrario, no accede al símbolo y el vinculador ignorará eso).Eso es todo amigos.
Oh, espera, hay una cosa más:
el enlazador tiene una opción llamada
-dead_strip
. ¿Qué hace esta opción? Si el vinculador decidió cargar un archivo de objeto, todos los símbolos del archivo de objeto se convierten en parte del binario vinculado, se utilicen o no. Por ejemplo, un archivo de objeto contiene 100 funciones, pero el binario solo usa una de ellas, las 100 funciones todavía se agregan al binario porque los archivos de objeto se agregan como un todo o no se agregan en absoluto. Agregar un archivo de objeto parcialmente no suele ser compatible con los vinculadores.Sin embargo, si le dice al enlazador que "haga una tira muerta", el enlazador primero agregará todos los archivos de objetos al binario, resolverá todas las referencias y finalmente escaneará el binario en busca de símbolos que no estén en uso (o solo en uso por otros símbolos que no estén en utilizar). Todos los símbolos que no se encuentran en uso se eliminan como parte de la etapa de optimización. En el ejemplo anterior, las 99 funciones no utilizadas se eliminan nuevamente. Esto es muy útil si usa opciones como
-load_all
,-force_load
oPerform Single-Object Prelink
porque estas opciones pueden explotar fácilmente los tamaños binarios en algunos casos y la eliminación completa eliminará nuevamente el código y los datos no utilizados.La eliminación completa funciona muy bien para el código C (por ejemplo, las funciones no utilizadas, las variables y las constantes se eliminan como se esperaba) y también funciona bastante bien para C ++ (por ejemplo, las clases no utilizadas se eliminan). No es perfecto, en algunos casos, algunos símbolos no se eliminan aunque estaría bien eliminarlos, pero en la mayoría de los casos funciona bastante bien para estos idiomas.
¿Qué hay de Obj-C? ¡Olvídalo! No hay desnudos para Obj-C. Como Obj-C es un lenguaje de características de tiempo de ejecución, el compilador no puede decir en tiempo de compilación si un símbolo está realmente en uso o no. Por ejemplo, una clase Obj-C no está en uso si no hay un código que haga referencia directa a ella, ¿correcto? ¡Incorrecto! Puede generar dinámicamente una cadena que contenga un nombre de clase, solicitar un puntero de clase para ese nombre y asignar dinámicamente la clase. Por ejemplo, en lugar de
Yo tambien podria escribir
En ambos casos
mmc
es una referencia a un objeto de la clase "MyCoolClass", pero no hay una referencia directa a esta clase en el segundo ejemplo de código (ni siquiera el nombre de la clase como una cadena estática). Todo sucede solo en tiempo de ejecución. Y eso a pesar de que las clases son en realidad símbolos reales. Es aún peor para las categorías, ya que ni siquiera son símbolos reales.Entonces, si tiene una biblioteca estática con cientos de objetos, pero la mayoría de sus archivos binarios solo necesitan unos pocos, puede preferir no usar las soluciones (1) a (4) anteriores. De lo contrario, terminará con binarios muy grandes que contienen todas estas clases, a pesar de que la mayoría de ellos nunca se utilizan. Para las clases, generalmente no necesita ninguna solución especial, ya que las clases tienen símbolos reales y siempre que los haga referencia directamente (no como en el segundo ejemplo de código), el vinculador identificará su uso bastante bien por sí mismo. Sin embargo, para las categorías, considere la solución (5), ya que permite incluir solo las categorías que realmente necesita.
Por ejemplo, si desea una categoría para NSData, por ejemplo, agregarle un método de compresión / descompresión, crearía un archivo de encabezado:
y un archivo de implementación
Ahora solo asegúrese de que
import_NSData_Compression()
se llame a cualquier parte de su código . No importa dónde se llame o con qué frecuencia se llama. En realidad, no es necesario llamarlo en absoluto, es suficiente si el vinculador lo cree así. Por ejemplo, podría poner el siguiente código en cualquier parte de su proyecto:No tiene que llamar nunca
importCategories()
a su código, el atributo hará que el compilador y el vinculador crean que se llama, incluso en caso de que no lo sea.Y un consejo final:
si agrega
-whyload
a la llamada de enlace final, el enlazador imprimirá en el registro de compilación qué archivo de objeto de qué biblioteca cargó debido a qué símbolo en uso. Solo imprimirá el primer símbolo considerado en uso, pero ese no es necesariamente el único símbolo en uso de ese archivo de objeto.fuente
-whyload
intentar depurar por qué el enlazador está haciendo algo puede ser bastante difícil!Dead Code Stripping
enBuild Settings>Linking
. ¿Es lo mismo que-dead_strip
agregado enOther Linker Flags
?-ObjC
, así que probé tu truco pero se queja"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not found
. Puseimport_NSString_jsonObject
mi Framework incrustado llamadoUtility
y agrego#import <Utility/Utility.h>
con una__attribute__
declaración al final de miAppDelegate.h
.Este problema se ha solucionado en LLVM . El arreglo se envía como parte de LLVM 2.9 La primera versión de Xcode que contiene el arreglo es el envío de Xcode 4.2 con LLVM 3.0. El uso de
-all_load
o-force_load
ya no es necesario cuando se trabaja con XCode 4.2-ObjC
.fuente
-ObjC
bandera todavía es necesaria y siempre lo será. La solución fue el uso de-all_load
o-force_load
. Y eso ya no es necesario. Arreglé mi respuesta arriba.Esto es lo que debe hacer para resolver este problema por completo al compilar su biblioteca estática:
Vaya a Configuración de compilación de Xcode y establezca Realizar enlace de objeto único en SÍ o
GENERATE_MASTER_OBJECT_FILE = YES
en su archivo de configuración de compilación.Por defecto, el enlazador genera un archivo .o para cada archivo .m. Entonces las categorías obtienen diferentes archivos .o. Cuando el vinculador mira los archivos .o de una biblioteca estática, no crea un índice de todos los símbolos por clase (el tiempo de ejecución lo hará, no importa qué).
Esta directiva le pedirá al vinculador que empaque todos los objetos juntos en un gran archivo .o y, de este modo, obliga al vinculador que procesa la biblioteca estática para obtener el índice de todas las categorías de clases.
Espero que eso lo aclare.
fuente
Un factor que rara vez se menciona cada vez que surge la discusión de vinculación de la biblioteca estática es el hecho de que también debe incluir las categorías en las fases de compilación-> copiar archivos y compilar las fuentes de la biblioteca estática .
Apple tampoco enfatiza este hecho en su recientemente publicado Uso de bibliotecas estáticas en iOS .
Pasé un día entero probando todo tipo de variaciones de -objC y -all_load, etc., pero no salió nada ... esta pregunta me llamó la atención. (no me malinterpretes ... todavía tienes que hacer las cosas -objC ... pero es más que eso).
También otra acción que siempre me ha ayudado es que siempre construyo la biblioteca estática incluida primero sola ... luego construyo la aplicación adjunta ...
fuente
Probablemente necesite tener la categoría en el encabezado "público" de su biblioteca estática: #import "MyStaticLib.h"
fuente