ventajas tutorial programación programacion objective lenguaje desventajas iphone objective-c static-libraries categories

iphone - tutorial - swift ios



Categorías de Objective-C en biblioteca estática (6)

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 todavía es necesario.

¿Puede orientarme sobre cómo vincular correctamente la biblioteca estática al proyecto de iPhone? Utilizo 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 las 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 aplicación de otros proyectos?


Esto es lo que debe hacer para resolver este problema por completo al compilar su biblioteca estática:

Vaya a Configuraciones de compilación de Xcode y establezca Ejecutar vínculo previo de un solo objeto a SÍ o GENERATE_MASTER_OBJECT_FILE = YES en su archivo de configuración de compilación.

De forma predeterminada, el vinculador genera un archivo .o para cada archivo .m. Entonces las categorías obtienen diferentes archivos .o. Cuando el enlazador mira los archivos .o de una biblioteca estática, no crea un índice de todos los símbolos por clase (Runtime lo hará, no importa qué).

Esta directiva le pedirá al vinculador que agrupe todos los objetos en un gran archivo .o y, con esto, fuerza al vinculador que procesa la biblioteca estática a obtener el índice de todas las categorías de clase.

Espero que lo aclare.


La respuesta de Vladimir en realidad es bastante buena, sin embargo, me gustaría dar algunos conocimientos más de fondo aquí. Quizás algún día alguien encuentre mi respuesta y la encuentre útil.

El compilador transforma los archivos de origen (.c, .cc, .cpp, .m) en archivos de objetos (.o). Hay un archivo de objeto por archivo fuente. Los archivos de objeto contienen símbolos, código y datos. Los archivos de objetos no son utilizables directamente por el sistema operativo.

Ahora, al compilar una biblioteca dinámica (.dylib), un marco, un paquete cargable (.bundle) o un archivo binario ejecutable, estos archivos de objetos se vinculan entre sí por el vinculador 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 objetos simplemente se agregan a un gran archivo de almacenamiento, de ahí la extensión de las bibliotecas estáticas (.a para el archivo). Por lo tanto, un archivo .a no es más que un archivo de archivos de objetos (.o). Piense en un archivo TAR o en un archivo ZIP sin compresión. Es más fácil copiar un archivo .a único que un conjunto completo de archivos .o (similar a Java, donde se almacenan los archivos .class en un archivo .jar para facilitar la 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 comprobará a cuál de estos símbolos se hace referencia mediante los binarios. Solo los archivos objeto que contienen los símbolos de referencia son cargados por el vinculador y son considerados por el proceso de vinculación. Por ejemplo, si su archivo tiene 50 archivos objeto, pero solo 20 contienen símbolos utilizados por el binario, solo esos 20 son cargados por el vinculador, los otros 30 se ignoran por completo en el proceso de enlace.

Esto funciona bastante bien para los códigos C y C ++, ya que estos lenguajes intentan hacer tanto como sea posible en tiempo de compilación (aunque C ++ también tiene algunas características de solo 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 enlazador puede ver si se hace referencia a una clase 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, este archivo de objeto lo cargará el vinculador porque el vinculador ve que se está utilizando un símbolo. Las categorías son solo una función 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 enlazador carga un archivo de objeto que contiene el código Obj-C, todas las partes de Obj-C siempre forman parte de la etapa de enlace. Entonces, si un archivo de objeto que contiene categorías se carga porque cualquier símbolo del mismo se considera "en uso" (ya sea una clase, ya sea una función, ya sea 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 no está cargado, las categorías que contiene 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 sabes cómo todo esto funciona en conjunto, echemos otro vistazo a la solución propuesta:

  1. Una solución es agregar -all_load a la llamada del enlazador. ¿Qué hará realmente esa bandera enlazadora? En realidad, le dice al vinculador 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.

  2. Otra solución es agregar -force_load a la llamada del enlazador, incluida la ruta al archivo. Este indicador funciona exactamente como -all_load , pero solo para el archivo especificado. Por supuesto, esto también funcionará.

  3. La solución más popular es agregar -ObjC a la llamada del enlazador. ¿Qué hará realmente esa bandera enlazadora? Este indicador le dice al vinculador " Cargar 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 funcionará también y no forzará la carga de archivos de objetos que no contengan código Obj-C (estos aún solo se cargan a pedido).

  4. Otra solución es la nueva configuración de compilación de Xcode. Perform Single-Object Prelink . ¿Qué hará esta configuración? Si está habilitado, todos los archivos de objeto (recuerde, hay uno por archivo de origen) se fusionan en un solo archivo de objeto (que no es un enlace real, de ahí el nombre PreLink ) y este único archivo de objeto (a veces también llamado "objeto maestro") archivo ") se agrega al archivo. Si ahora se considera que se está utilizando algún símbolo del archivo de objeto maestro, se considera que todo el archivo de objeto maestro está en uso y, por lo tanto, todas las partes de Objective-C siempre se cargan. Y dado que las clases son símbolos normales, basta con usar una única clase de dicha biblioteca estática para obtener también todas las categorías.

  5. La solución final es el truco que Vladimir agregó al final de su respuesta. Coloque un " símbolo falso " en cualquier archivo fuente declarando solo categorías. Si desea utilizar cualquiera de las categorías en tiempo de ejecución, asegúrese de que de alguna manera hace referencia al símbolo falso en tiempo de compilación, ya que esto hace que el archivo objeto sea cargado por el enlazador y, por lo tanto, también todo el código Obj-C. Por ejemplo, podría ser una función con un cuerpo de función vacío (que no hará nada al ser llamado) o podría ser una variable global a la que se accede (por ejemplo, una int global una vez leída o una vez 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 accederá al símbolo y el enlazador 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 enlazador decidió cargar un archivo objeto, todos los símbolos del archivo objeto se vuelven parte del binario vinculado, ya sea que se utilicen o no. Por ejemplo, un archivo de objeto contiene 100 funciones, pero el binario solo utiliza una de ellas, las 100 funciones se agregan al binario porque los archivos de objeto se agregan como un todo o no se agregan en absoluto. Los enlazadores no pueden agregar parcialmente un archivo de objeto.

Sin embargo, si le dices al enlazador "tira muerta", el enlazador primero agregará todos los archivos del objeto al binario, resolverá todas las referencias y finalmente escaneará el binario para ver si hay símbolos que no estén en uso (o solo para otros símbolos que no estén en uso) utilizar). Todos los símbolos que se encuentran que no están en uso se eliminan luego 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 utiliza opciones como -load_all , -force_load o Perform Single-Object Prelink porque estas opciones pueden hacer explotar fácilmente los tamaños binarios de forma espectacular en algunos casos y la eliminación de datos Perform Single-Object Prelink eliminará nuevamente el código y los datos no utilizados.

Dead Stripping 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, se eliminan las clases no utilizadas). 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 eliminación de muertos 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 directamente a ella, ¿correcto? ¡Incorrecto! Puede construir 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

MyCoolClass * mcc = [[MyCoolClass alloc] init];

Yo también escribiría

NSString * cname = @"CoolClass"; NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname]; Class mmcClass = NSClassFromString(cnameFull); id mmc = [[mmcClass alloc] init];

En ambos casos, mmc es una referencia a un objeto de la clase "MyCoolClass", pero no hay referencia directa a esta clase en el segundo ejemplo de código (ni siquiera el nombre de clase como una cadena estática). Todo sucede solo en tiempo de ejecución. Y eso a pesar de que las clases son realmente 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, sin embargo, la mayoría de sus binarios solo necesitan algunos de ellos, es posible que prefiera no usar las soluciones (1) a (4) anteriores. De lo contrario, terminas con binarios muy grandes que contienen todas estas clases, aunque 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 las referencia directamente (no como en la segunda muestra 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 solo permite incluir las categorías que realmente necesita.

Por ejemplo, si quiere una categoría para NSData, por ejemplo, agregarle un método de compresión / descompresión, debería crear un archivo de encabezado:

// NSData+Compress.h @interface NSData (Compression) - (NSData *)compressedData; - (NSData *)decompressedData; @end void import_NSData_Compression ( );

y un archivo de implementación

// NSData+Compress @implementation NSData (Compression) - (NSData *)compressedData { // ... magic ... } - (NSData *)decompressedData { // ... magic ... } @end void import_NSData_Compression ( ) { }

Ahora solo asegúrese de import_NSData_Compression() cualquier parte de su código import_NSData_Compression() . No importa dónde se llame o con qué frecuencia se llame. En realidad, no tiene que ser llamado en absoluto, es suficiente si el enlazador lo cree. Por ejemplo, podría poner el siguiente código en cualquier lugar de su proyecto:

__attribute__((used)) static void importCategories () { import_NSData_Compression(); // add more import calls here }

No tiene que llamar siempre a importCategories() en su código, el atributo hará que el compilador y el enlazador 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 vinculador imprimirá en el registro de compilación qué archivo de objeto de qué biblioteca se cargó debido a qué símbolo está 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 objeto.


Probablemente necesites tener la categoría en el encabezado "público" de tu biblioteca estática: #import "MyStaticLib.h"


Un factor que rara vez se menciona siempre que aparece la biblioteca estática que vincula la discusión 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 en sí misma .

Apple tampoco enfatiza este hecho en sus Bibliotecas estáticas recientemente publicadas en iOS tampoco.

Pasé todo un día probando todo tipo de variaciones de -objC y -all_load, etc., pero no salió nada de eso ... this pregunta me llamó la atención sobre este tema. (No me malinterpreten ... todavía tiene que hacer las cosas -objC ... pero es más que eso).

Otra acción que siempre me ha ayudado es que siempre construyo la biblioteca estática incluida por sí sola. Luego construyo la aplicación adjunta.


Solución: a partir de Xcode 4.2, solo necesita ir a la aplicación que está enlazando con la biblioteca (no la biblioteca) y hacer clic en el proyecto en el Navegador de proyectos, hacer clic en el objetivo de su aplicación, luego crear configuraciones y luego buscar "Otro Indicador de Enlazador ", haga clic en el botón + y agregue ''-ObjC''. ''-all_load'' y ''-force_load'' ya no son necesarios.

Detalles: Encontré algunas respuestas en varios foros, blogs y documentos de Apple. Ahora intento 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 ):

Objective-C no define los símbolos del enlazador para cada función (o método, en Objective-C); en su lugar, los símbolos del enlazador solo se generan para cada clase. Si amplía una clase preexistente con categorías, el vinculador no sabe asociar el código objeto de la implementación de la clase principal y la implementación de la categoría. Esto evita que los objetos creados en la aplicación resultante respondan a un selector que está definido en la categoría.

Y su solución:

Para resolver este problema, la biblioteca estática debe pasar la opción -ObjC al enlazador. Este indicador hace que el enlazador cargue cada archivo de objeto en la biblioteca que define una clase o categoría Objective-C. Si bien esta opción generalmente dará como resultado un archivo ejecutable más grande (debido a un código de objeto adicional cargado en la aplicación), permitirá la creación exitosa de bibliotecas estáticas efectivas Objective-C que contienen categorías en las clases existentes.

y también hay recomendaciones en las Preguntas frecuentes sobre el desarrollo de iPhone:

¿Cómo puedo vincular todas las clases de Objective-C en una biblioteca estática? Establezca la configuración de creación de Indicadores del otro enlazador en -ObjC.

y descripciones de banderas:

- all_load Carga todos los miembros de bibliotecas de archivos estáticos.

- ObjC Carga todos los miembros de bibliotecas de archivos estáticos que implementan una clase o categoría Objective-C.

- force_load (path_to_archive) Carga todos los miembros de la biblioteca de archivos estáticos especificada. Nota: -all_load fuerza a todos los miembros de todos los archivos a ser cargados. Esta opción te permite apuntar a un archivo específico.

* 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: posiblemente, el proyecto de dependencia directa no se agregó correctamente. Cuando lo elimine y agregue nuevamente con los pasos:

  1. Arrastre y suelte el archivo del proyecto lib en el proyecto de la aplicación (o agréguelo con Proyecto-> Agregar al proyecto ...).
  2. Haga clic en la flecha en el ícono del proyecto lib - se muestra el nombre del archivo mylib.a, arrastre este archivo mylib.a y suéltelo en Target -> Link Binary With Library group.
  3. Abra la información de destino en la primera página (General) y agregue mi lib a la lista de dependencias

después de eso todo funciona bien. La bandera "-ObjC" fue suficiente en mi caso.

También me interesó la idea de http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html blog. El autor dice que puede usar la categoría de lib sin establecer -all_load o -ObjC flag. Simplemente agrega a los archivos de categoría h / m una interfaz / implementación de clase ficticia vacía para forzar al enlazador a usar este archivo. Y sí, este truco hace el trabajo.

Pero el autor también dijo que incluso no creó una instancia de objeto ficticio. Mm ... Como he encontrado, deberíamos llamar explícitamente a un código "real" del archivo de categoría. Por lo tanto, al menos se debe llamar a la función de clase. E incluso no necesitamos clase ficticia. La función c individual hace lo mismo.

Entonces, si escribimos archivos lib como:

// mylib.h void useMyLib(); @interface NSObject (Logger) -(void)logSelf; @end // mylib.m void useMyLib(){ NSLog(@"do nothing, just for make mylib linked"); } @implementation NSObject (Logger) -(void)logSelf{ NSLog(@"self is:%@", [self description]); } @end

y si llamamos a useMyLib (); en cualquier lugar del proyecto de la aplicación, en cualquier clase podemos usar el método de categoría logSelf;

[self 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