[notas de estudio de iOS]: hable sobre la biblioteca estática y la biblioteca dinámica

[notas de estudio de iOS]: hable sobre la biblioteca estática y la biblioteca dinámica

En el desarrollo de proyectos de iOS, usamos bibliotecas estáticas y bibliotecas dinámicas todo el tiempo, sin el apoyo de las bibliotecas, sería muy difícil. Por ejemplo, si desea dibujar una interfaz, no puede prescindir de la biblioteca UIKit. Las diversas estructuras de datos básicas que desea utilizar, como NSString, NSArray, etc., también son inseparables de la biblioteca Foundation. Además de la biblioteca oficial, también descargaremos bibliotecas de código abierto de terceros de comunidades de código abierto como Github para usarlas durante el desarrollo. Generalmente, las bibliotecas de terceros que usamos o las bibliotecas desarrolladas por nosotros mismos se usan como bibliotecas estáticas, mientras que la mayoría de las bibliotecas proporcionadas por el sistema son bibliotecas dinámicas, que son convenientes para compartir procesos múltiples. Aunque usamos bibliotecas todos los días, ¿realmente entiendes las bibliotecas estáticas y las bibliotecas dinámicas? ¿Cuál es la estructura de la biblioteca estática y la biblioteca dinámica? ¿Cuál es la diferencia entre biblioteca estática y biblioteca dinámica? ¿Cómo se aplican? En este blog hablaremos de estos temas.

1. Introducción

Hay muchas similitudes entre las bibliotecas estáticas y las bibliotecas dinámicas y, por supuesto, también hay muchas diferencias.

Por el nombre del sufijo, el archivo de biblioteca con el sufijo .a es una biblioteca estática y el archivo de biblioteca con el sufijo .dylib es una biblioteca dinámica. En el desarrollo de iOS, más a menudo las bibliotecas que usamos tienen el sufijo .framework. El marco puede ser una biblioteca estática o una biblioteca dinámica. El marco en sí mismo es un método de empaquetado. Sabemos que cuando escribimos código, escribimos "código fuente", y para que la computadora entienda este código fuente, necesitamos un compilador para compilar el código fuente y compilarlo en "código de máquina" que la computadora pueda entender. Un archivo fuente de .o se compilará en un archivo binario .o, ya sea una biblioteca estática o una biblioteca dinámica que sea una colección de archivos .o. No es suficiente que los desarrolladores solo tengan archivos de biblioteca compuestos por archivos .o. Es imposible para nosotros llamar fácilmente a los métodos en la biblioteca sin archivos de encabezado durante el desarrollo. Por lo tanto, los archivos de encabezado también son necesarios para convertir la biblioteca. La interfaz proporcionada en la interfaz está expuesto y, a veces, es posible que se necesiten otros recursos. Por ejemplo, la biblioteca relacionada con la página tendrá algunos recursos de imagen incorporados, etc. La función del marco es empaquetar los archivos de biblioteca, archivos de encabezado , y archivos de recursos juntos, lo cual es conveniente Lo usamos. La siguiente figura describe la relación entre los archivos de marco y los archivos de biblioteca:

2. Crea una biblioteca estática

Antes de obtener una comprensión más profunda de las bibliotecas estáticas, podemos crear una experiencia de biblioteca estática. Primero, use Xcode para crear un nuevo proyecto y seleccione Framework, como se muestra en la siguiente figura:

La plantilla de proyecto de marco creada generará un archivo de encabezado con el mismo nombre que el proyecto y una carpeta de recursos de recursos. Podemos crear nuevos archivos de clase funcional. Por ejemplo, podemos crear una nueva clase llamada MyLog y una clase MyTool. Código como sigue:

MiRegistro.h

// MyLog.h
#import <Foundation/Foundation.h>

@interface MyLog : NSObject

+ (void)log:(NSString *)str;

@end

MiRegistro.m

#import "MyLog.h"

@implementation MyLog

+ (void)log:(NSString *)str {
    NSLog(@"MyLog:%@",str);
}

@end

MiHerramienta.h

#import <Foundation/Foundation.h>

@interface MyTool : NSObject

+ (NSInteger)add:(NSInteger)a another:(NSInteger)b;

@end

MiHerramienta.m

#import "MyTool.h"

@implementation MyTool

+ (NSInteger)add:(NSInteger)a another:(NSInteger)b {
    return a + b;
}

@end

En el archivo de encabezado de biblioteca generado de forma predeterminada, estos dos archivos de encabezado de función se introducen de la siguiente manera:

#import <Foundation/Foundation.h>

//! Project version number for MyStatic.
FOUNDATION_EXPORT double MyStaticVersionNumber;

//! Project version string for MyStatic.
FOUNDATION_EXPORT const unsigned char MyStaticVersionString[];

#import "MyLog.h"
#import "MyTool.h"

Antes de construir frameworkwrok, podemos configurar el marco para que se construya como una biblioteca dinámica dinámica o una biblioteca estática. Primero lo construimos como una biblioteca estática y configuramos el tipo Mach-o de la opción de compilación en Biblioteca estática, de la siguiente manera:

Después de eso, puede dejar que Xcode construya y luego puede encontrar el archivo de marco generado en la carpeta Productos correspondiente, como se muestra en la siguiente figura:

Si observa el contenido del paquete de este archivo de marco, encontrará que hay 5 tipos de archivos, de la siguiente manera:

Entre ellos, el archivo de firma del marco se almacena en _CodeSignature.

Los archivos de encabezado se almacenan en los encabezados. Cabe señalar que al compilar el proyecto de marco, los archivos de encabezado que deben exponerse deben configurarse como públicos.

El archivo Info.plist es el archivo de configuración del marco actual.

El archivo modulemap en Módulos se usa para administrar el mapa del módulo LLVM y definir la estructura del componente.

A continuación, podemos intentar usar esta biblioteca estática, usar Xcode para crear un nuevo proyecto de iOS llamado LibDemo, arrastrar el archivo MyStatic.framework creado anteriormente en este proyecto y encontrar Framework Search Paths and Header en las opciones de compilación del proyecto en Search Paths, configure la ruta del marco y la ruta del archivo de encabezado respectivamente, como se muestra en la siguiente figura:

Modifique el archivo ViewController.m del proyecto de prueba de la siguiente manera:

#import "ViewController.h"
#import "MyStatic.framework/Headers/MyStatic.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
}


@end

Al ejecutar el código, podemos ver desde la consola que nuestra biblioteca estática funciona correctamente. Puede pensar que la forma de importar los archivos de encabezado anteriores es muy fea. Puede crear una nueva carpeta en el proyecto y copiar los archivos de encabezado en el paquete del marco, como se muestra a continuación:

De esta manera, puede usar las funciones en el marco como hacer referencia a los archivos de encabezado en el proyecto:

#import "ViewController.h"
#import "MyStatic.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
}

@end

3. Pruebe la biblioteca dinámica

El proceso de creación y uso de bibliotecas estáticas parece muy fácil, y las bibliotecas dinámicas deberían ser similares. Probémoslo ahora, use Xcode para crear un nuevo proyecto de marco llamado MyDylib, cambie el tipo de Mach-O en las opciones de compilación a Biblioteca dinámica y cree algunas clases de prueba simples de la siguiente manera:

MiObjetoUno.h

#import <Foundation/Foundation.h>

@interface MyObjectOne : NSObject

@property(copy) NSString *name;

@end

MiObjetoUno.m

#import "MyObjectOne.h"

@implementation MyObjectOne

@end

MiObjetoDos.h

#import <Foundation/Foundation.h>

@interface MyObjectTwo : NSObject

@property(copy) NSString *title;

@end

MiObjetoDos.m

#import "MyObjectTwo.h"

@implementation MyObjectTwo

@end

De la misma manera, arrastre el archivo de marco integrado al proyecto de prueba, configure la ruta del archivo de encabezado y agregue el código de prueba de la siguiente manera:

#import "ViewController.h"
#import "MyStatic.h"
#import "MyDylib.framework/Headers/MyDylib.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
    
    MyObjectOne *one = [[MyObjectOne alloc] init];
    one.name = @"Hello";
    [MyLog log:one.name];
}

Intente compilar y ejecutar.Hasta ahora, todo parece estar normal, pero cuando el programa se ejecuta, se bloqueará y la consola mostrará la siguiente información:

dyld[72035]: Library not loaded: @rpath/MyDylib.framework/MyDylib

El motivo de esta excepción es que no se encontró el archivo de biblioteca dinámica. Apareció la diferencia entre la biblioteca dinámica de la biblioteca estática y la biblioteca dinámica. Cómo resolver este problema es realmente muy simple. Encontramos el archivo ejecutable de salida compilado por el proyecto de prueba actual, haga clic para mostrar el contenido del paquete, cree una nueva carpeta Frameworks en él y copie el archivo MyDylib.framework en él, como se muestra en la siguiente figura:

Ahora ejecute el proyecto nuevamente, encontrará que el programa se puede ejecutar normalmente. Sin embargo, la operación de copiar manualmente la biblioteca dinámica al archivo ejecutable es muy poco elegante.Si realmente queremos usar la biblioteca dinámica en el proyecto, usaremos más a menudo scripts automatizados para realizar el paso de copiar los archivos de la biblioteca.

A través de estas prácticas, parece que podemos sentir la diferencia entre bibliotecas estáticas y bibliotecas dinámicas, pero ¿cuál es la diferencia? Seguimos explorando con preguntas.

4. La diferencia entre biblioteca estática y biblioteca dinámica

Ⅰ.Cómo cargar

La razón por la que no se puede encontrar la biblioteca dinámica anterior es que el método de carga de la biblioteca dinámica y la biblioteca estática es diferente.

Biblioteca estática: cuando la biblioteca estática está vinculada, se copiará completamente en el archivo ejecutable. Si hay varias aplicaciones que usan la misma biblioteca estática, habrá un código de biblioteca estática completo en el archivo binario de cada aplicación.

Biblioteca dinámica: cuando el programa está vinculado, la biblioteca dinámica no se copia en el archivo binario, sino que el sistema la carga dinámicamente en la memoria para que el programa la llame cuando se está ejecutando. Debido a esta función, el sistema de biblioteca dinámica se puede cargar solo una vez y compartir con la aplicación.

Para el método de carga de la biblioteca dinámica de la biblioteca estática, podemos ir un paso más allá. En primer lugar, la biblioteca estática se copiará por completo en el archivo ejecutable. La integridad aquí es realmente inexacta. Cuando introducimos bibliotecas de terceros, a menudo necesitamos configurar la opción -Objc en las Otras banderas del enlazador del proyecto. La función de este elemento es establecer la optimización del enlace.

Por defecto, cuando se vincula la biblioteca estática, no copiará todo el código al archivo ejecutable, solo copiará el código usado, lo que puede reducir el tamaño del paquete final de la aplicación, pero el dinamismo del lenguaje OC lo determina. no es necesario consultar el código directamente, y este método de conexión a menudo causa problemas de tiempo de ejecución.

Después de configurar la opción -Objc, el enlazador cargará todas las clases de OC y sus Categorías correspondientes independientemente de si se usa el código o no.

Después de configurar la opción -all_load, el enlazador cargará todos los archivos de objetos, no solo los archivos OC.

Establecer el parámetro -force_load puede especificar forzar la carga de todos los archivos de objetos de una biblioteca estática. Para esta biblioteca estática, el efecto es el mismo que -all_load.

Para las bibliotecas dinámicas, el vinculador no tiene forma de realizar dicha optimización, porque la biblioteca dinámica se carga en tiempo de ejecución y el vinculador no sabe qué código se utilizará. La optimización del tamaño parece ser mejor que la biblioteca dinámica, pero ¿lo es? ¿realmente tan? Dejamos un presagio primero, para luego analizarlo después.

Ⅱ La estructura del archivo es diferente

La diferencia esencial entre una biblioteca estática y una biblioteca dinámica es que la estructura de los archivos construidos es completamente diferente. Los archivos de la biblioteca se pueden ver con la herramienta MachOView.

Hablemos primero de la biblioteca estática.La estructura de la biblioteca estática abierta por MachOView es la siguiente:

Se puede ver que la estructura de la biblioteca estática es en realidad relativamente simple. Excepto por algunos archivos de descripción y tablas de símbolos de la propia biblioteca, es básicamente una colección de otros archivos ejecutables. Como puede ver en la figura, cada archivo ejecutable tendrá algunos datos de encabezado, estos datos de encabezado registran el nombre del ejecutable, el tamaño y otra información. Puede hacer clic en cualquier archivo ejecutable, que contiene varios segmentos de código, segmentos de datos y otros datos con los que estamos familiarizados:

Veamos de nuevo la biblioteca dinámica, su estructura es la siguiente:

Se puede ver que la biblioteca dinámica en sí es un archivo ejecutable, que no es una simple colección de todos los archivos .o internos, sino un archivo de imagen que finalmente se vincula. Dado que la biblioteca dinámica está vinculada en tiempo de ejecución, no se puede optimizar en tiempo de compilación, lo que puede aumentar el tamaño del paquete de la aplicación, pero en aplicaciones prácticas, usamos principalmente el parámetro -Objc para obligar a la biblioteca estática a vincular todos los archivos OC, y Cada archivo .o en la biblioteca estática tendrá una información de encabezado, mientras que la biblioteca dinámica omite esta parte de la información, por lo que, al final, la biblioteca estática no es necesariamente mejor en términos de afectar el tamaño del paquete de la aplicación. Pero una cosa es cierta, las bibliotecas estáticas están vinculadas en el momento de la compilación, lo que ahorrará tiempo de inicio de la aplicación. A menudo, cuando se realizan proyectos de optimización, no hay un plan fijo, tenemos que elegir el plan más adecuado de acuerdo con la situación real.

5. Biblioteca dinámica y tiempo de ejecución

Ⅰ Carga de librerías dinámicas

Cuando se trata de tiempos de ejecución, los desarrolladores tienen mucho que hacer. En primer lugar, pensemos en el proyecto de prueba anterior. Si no copiamos el archivo de la biblioteca dinámica en el paquete IPA, ¿por qué el programa no puede ejecutar encontrar el archivo de la biblioteca? ¿Y por qué necesitamos copiar la biblioteca dinámica en la carpeta Frameworks del paquete IPA? ¿Ninguna otra carpeta?

Para explicar el problema anterior, todavía tenemos que mirar el principio de carga de las bibliotecas dinámicas.Puede usar MachOView para abrir el archivo ejecutable del paquete de la aplicación de prueba y encontrar la sección Cargar comandos en él, como se muestra en la siguiente figura:

Se puede ver que hay algunas instrucciones de carga para bibliotecas dinámicas. Foundation, UIKit, etc. son todas bibliotecas dinámicas del sistema. Podemos ver la ruta de carga detallada en sus detalles, de la siguiente manera:

Para nuestra propia biblioteca MyDylib, la ruta de carga es la siguiente:

Se puede ver que esta biblioteca dinámica se carga desde la ruta @rpath/MyDylib.framework/MyDylib. La configuración de esta ruta de carga se ha determinado cuando se compila la biblioteca dinámica. Podemos ver el proyecto MyDylib, que se compila y configurado en Opciones de Xcode, busque la opción Nombre de instalación de biblioteca dinámica, como se muestra a continuación:

El @rpath aquí es en realidad una variable de entorno. Puede configurar el valor de @rpath en el proyecto de la aplicación. Busque rpath en las opciones de compilación del proyecto LibDemo, y podrá ver la configuración de esta variable de entorno:

Ahora que sabemos que el archivo de la biblioteca dinámica no tiene que colocarse en la carpeta Frameworks, modifique la ruta de la variable @rpath para modificar la ruta de carga de la biblioteca dinámica.

Para este método de carga de la biblioteca dinámica, en principio, podemos modificar la ruta de carga del archivo binario o reemplazar directamente el archivo de la biblioteca dinámica en el paquete para lograr algunas funciones de inyección inversa, lo cual es genial.

Ⅱ Biblioteca dinámica de carga de código

Las bibliotecas dinámicas se cargan en tiempo de ejecución y también podemos usar código para controlar dinámicamente la carga de bibliotecas dinámicas en tiempo de ejecución. Puede eliminar todas las referencias a MyDylib en el proyecto de prueba y también eliminar la ruta del archivo de encabezado configurado. Copiamos esta biblioteca dinámica en el paquete del proyecto, de la siguiente manera:

El código para modificar la clase ViewController es el siguiente:

#import "ViewController.h"
#import "MyStatic.h"
#import <dlfcn.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
    
    NSString *path = [[[NSBundle mainBundle] pathForResource:@"MyDylib" ofType:@"framework"] stringByAppendingString:@"/MyDylib"];
    // 载入动态库
    void * p = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_LAZY);
    if (p) {
        // 加载动态库成功 直接使用
        Class cls = NSClassFromString(@"MyObjectOne");
        NSObject *obj = [[cls alloc] init];
        [obj performSelector:@selector(setName:) withObject:@"Hello"];
        [MyLog log:[obj performSelector:@selector(name)]];
    }
}

@end

En este punto, compile y ejecute el proyecto nuevamente. Si observa el archivo binario del proyecto de prueba, no hay MyDylib cargado en el comando de carga, pero el programa aún puede ejecutarse normalmente. La función de la función dlopen es cargar el enlace dinámico en tiempo de ejecución Después de que la biblioteca se carga correctamente, podemos llamar directamente al código en la biblioteca dinámica con la ayuda del método de tiempo de ejecución de OC. De esta manera, podemos realmente realizar la descarga dinámica y el uso del complemento, de modo que la aplicación tenga una capacidad de actualización en caliente muy alta, pero debe tenerse en cuenta que la forma de descargar dinámicamente la biblioteca dinámica no está permitida. puesto en la AppStore, solo podemos probar la aplicación o usarla en aplicaciones corporativas.

Yendo un paso más allá, de hecho, la biblioteca dinámica no se lee necesariamente desde el sandbox local. Al depurar localmente, puede leer el archivo de la biblioteca dinámica desde cualquier ubicación y cargarlo, lo que puede realizar muchas funciones interesantes localmente, como Inyección herramienta, que monitorea los cambios de los archivos de código a través de un servicio, luego los empaqueta en una biblioteca dinámica y los inyecta en el programa, y ​​luego reemplaza las clases y los métodos en el tiempo de ejecución para lograr el efecto de actualización en caliente de los proyectos iOS desarrollados localmente, lo cual es muy fácil de usar.

Concéntrese en la tecnología, comprenda el amor, esté dispuesto a compartir, sea un amigo

Pregunta rápida: 316045346

{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/2340880/blog/5323143
Recomendado
Clasificación