Tiempo de ejecución y reenvío de mensajes en iOS

A principios de la década de 1980, Xiao Li y Xiao Wang eran una pareja de larga distancia. Bajo el liderazgo del cuerno de la reforma, Xiao Wang eligió decididamente una ciudad en el sur para luchar. En ese momento, no había teléfono móvil. Confíe en escribir cartas. Sin embargo, debido a que Xiao Wang viaja con frecuencia, su dirección residencial cambiará con frecuencia. Por lo tanto, cada vez que Xiao Li envía una respuesta a Xiao Wang, es posible que Xiao Wang no la reciba debido al cambio de dirección. Más tarde, pensaron en una buena manera de resolver este problema. El método específico es el siguiente:

Reenvío de mensajes en los años 80

 

De hecho, la imagen de arriba básicamente puede expresar la función de Runtime en iOS y el mecanismo de reenvío de mensajes de iOS. La característica de Runtime es principalmente la entrega de mensajes (métodos). Si el mensaje (método) no se encuentra en el objeto, se reenviará. ¿Cómo se hace? Exploramos el mecanismo de implementación de Runtime desde los siguientes aspectos.

Introducción al tiempo de ejecución

Objective-C amplía el lenguaje C y agrega funciones orientadas a objetos y un mecanismo de mensajería al estilo Smalltalk. El núcleo de esta extensión es una biblioteca en tiempo de ejecución escrita en C y en lenguaje compilado. Es la piedra angular de los mecanismos dinámicos y orientados a objetos de Objective-C.

Objective-C es un lenguaje dinámico, lo que significa que no solo requiere un compilador, sino también un sistema de tiempo de ejecución para crear clases y objetos dinámicamente, y realizar el paso y reenvío de mensajes. Comprender el mecanismo de tiempo de ejecución de Objective-C puede ayudarnos a comprender mejor el lenguaje y, cuando sea apropiado, el lenguaje puede extenderse para resolver algunos problemas técnicos o de diseño en el proyecto desde el nivel del sistema. Para comprender el tiempo de ejecución, primero debemos comprender su mensajería central (mensajería).

Para convertirse en un archivo ejecutable, un lenguaje de programación de alto nivel debe compilarse en lenguaje ensamblador y luego ensamblarse en lenguaje máquina. El lenguaje máquina también es el único lenguaje que la computadora puede reconocer, pero OC no se puede compilar directamente en lenguaje ensamblador, sino que debe transcribirse a C. Luego, el lenguaje se compila y ensambla, y la transición del lenguaje OC al lenguaje C se realiza en tiempo de ejecución. Sin embargo, utilizamos OC para el desarrollo orientado a objetos, y el lenguaje C es un desarrollo más orientado a procesos, lo que requiere la conversión de clases orientadas a objetos en estructuras orientadas a procesos.

Todas las anteriores son interpretaciones de documentos oficiales, que son un poco oscuras y aburridas. A continuación, expliquemos en detalle con el código.

Mensajería en tiempo de ejecución

Para un método de objeto  [obj test] , el compilador lo convierte en un mensaje para enviar objc_msgSend (obj, test) . El proceso ejecutado en Runtime es así:

1. Primero, encuentre su clase a través del puntero isa de obj ;

2. Busque la prueba en la lista de métodos de la clase ;

3. Si no hay ninguna prueba en la clase , continúe buscando su superclase ;

4. Una vez que se encuentra la función de prueba , ejecute su implementación IMP

Por supuesto, debido a problemas de eficiencia, no es razonable recorrer el objc_method_list una vez para cada mensaje. Por lo tanto, es necesario almacenar en caché las funciones a las que se llama con frecuencia para mejorar la eficiencia de la consulta de funciones. Esto es lo que hace objc_cache, otro miembro importante de objc_class: después de encontrar la prueba, guarde el nombre_método de la prueba como clave y el valor de method_imp. Cuando el mensaje de prueba se recibe nuevamente, se puede encontrar directamente en el caché para evitar atravesar objc_method_list. En el código fuente anterior, puede ver que objc_cache existe en la estructura objc_class.

El método de objec_msgSend:

OBJC_EXPORTidobjc_msgSend (idself, SEL op, ...)

Echemos un vistazo a las estructuras de objetos, clases y métodos:

Objeto de clase (objc_class)

La clase Objective-C está representada por el tipo Class, que en realidad es un puntero a la estructura objc_class

La estructura struct objc_class define muchas variables. La estructura guarda el puntero a la clase padre, el nombre de la clase, la versión, el tamaño de la instancia, la lista de variables de instancia, la lista de métodos, el caché, la lista de protocolos de cumplimiento, etc. Se puede ver que el objeto de clase es una estructura struct objc_class, esta estructura Los datos almacenados en el cuerpo son metadatos.

Instancia (objc_object)

 

Los metadatos del objeto de clase almacenan información sobre cómo crear una instancia, que se crea a partir de la estructura a la que apunta el puntero isa. El puntero isa del objeto de clase apunta a la metaclase.

Toda la información necesaria para crear objetos de clase y métodos de clase se guarda en la metaclase, por lo que toda la estructura debe ser como se muestra en la siguiente figura:

Objeto de instancia, objeto de clase y diagrama de metaclase

El puntero isa de la estructura struct objc_object apunta al objeto de la clase;

El puntero isa del objeto de clase apunta a la metaclase;

El puntero super_class apunta al objeto de clase de la clase padre;

El puntero de super_clase de la metaclase apunta a la metaclase de la clase principal;

Se siente como un trabalenguas, por lo que puede ser representado por un mapa de dioses en Internet:

Figura 6 Bucle autocerrado de objeto de instancia, objeto de clase y metaclase

De la figura de arriba, podemos ver que todo el sistema constituye un ciclo auto-cerrado. Si se hereda de NSObject, la clase Root en la figura de arriba es NSObject.

 

c1 es una clase obtenida a través de un objeto de instancia. El objeto de instancia puede obtener su objeto de clase. El nombre de clase representa al objeto de clase cuando es el destinatario del mensaje. Por lo tanto, el objeto de clase obtiene la clase en sí.

Si queremos obtener el objeto del puntero ISA, podemos usar las siguientes dos funciones

OBJC_EXPORTBOOLclass_isMetaClass (Classcls) OBJC_AVAILABLE (10.5, 2.0, 9.0, 1.0);

OBJC_EXPORTClassobject_getClass (idobj) OBJC_AVAILABLE (10.5, 2.0, 9.0, 1.0);

class_isMetaClass se usa para determinar si el objeto Class es una metaclase, y object_getClass se usa para obtener el objeto al que apunta el puntero isa del objeto.

 

Se puede ver en el código que la clase obtenida por un objeto de instancia a través del método de clase es el objeto de clase al que apunta su puntero isa, mientras que el objeto de clase no es una metaclase, y el objeto al que apunta el puntero isa del objeto de clase es una metaclase.

Entonces, sobre la parte del tiempo de ejecución, resumamos: primero, el objeto de instancia es una estructura. Esta estructura tiene solo una variable miembro, que apunta al objeto de clase que la construyó. Este objeto de clase almacena toda la información que necesita el objeto de instancia, incluidas las variables de instancia, Los métodos de instancia, etc., mientras que los objetos de clase se crean a través de metaclases, las variables de clase y los métodos de clase se almacenan en la metaclase, lo que explica perfectamente cómo toda la clase y la instancia se asignan a la estructura. Entonces, comprender el tiempo de ejecución es comprender el almacenamiento de datos de iOS y la relación y función entre sus clases, instancias, objetos de clase y metaclases en tiempo de ejecución.

Mecanismo de reenvío de mensajes

 Lo anterior mencionó una gran cantidad de conocimientos y conceptos básicos de tiempo de ejecución. ¿Qué tiene que ver con el reenvío de mensajes y cómo usarlo? Se trata del mecanismo de reenvío de mensajes de iOS.

En el análisis final, todas las llamadas a métodos en Objective-C básicamente envían mensajes a objetos.

1. Cree un método en la clase- (void) todoSomething;

2. El sistema iOS crea un número para este método, a saber: SEL (todoSomething) y lo agrega a la lista de métodos. (Selector es una instancia de SEL, que es diferente de IMP, que es un puntero a la dirección de memoria del programa de implementación final)

3. Al llamar a este método: [Objeto todoSomething], el sistema va a la lista de métodos para insertar este número de método y ejecutarlo cuando lo encuentre.

Nota: Cuando escribimos código C, a menudo usamos la sobrecarga de funciones, es decir, el nombre de la función es el mismo pero los parámetros son diferentes, pero esto no es factible en Objective-C, porque el selector solo recuerda el nombre del método y no los parámetros. Entonces no hay forma de distinguir entre diferentes métodos.

Por lo tanto, si se llama a un método, el mensaje se enviará una vez y la lista de métodos se buscará en el objeto de clase relacionado. Si no se encuentra, buscará en el árbol de herencia hasta que se encuentre la raíz del árbol de herencia (generalmente NSObject). Si falla y el reenvío de mensajes falla, ejecute el método doesNotRecognizeSelector: e informe un error de selector no reconocido. Entonces, ¿qué es exactamente el reenvío de mensajes? Las siguientes tres oportunidades se presentarán una por una.

1. Análisis de método dinámico

Objective-C llamará a + resolveInstanceMethod: o + resolveClassMethod: en tiempo de ejecución para darle la oportunidad de proporcionar una implementación de función. Si agrega una función y devuelve SÍ, el sistema en tiempo de ejecución reiniciará el proceso de envío de mensajes. Ejemplo como se muestra a continuación

Impreso "Doing foo"

Se puede ver que aunque la función foo: no está implementada, agregamos dinámicamente la función fooMethod a través de class_addMethod y ejecutamos el IMP de la función fooMethod. A partir de los resultados impresos, se realizó con éxito.

Si el método de resolución devuelve NO, el tiempo de ejecución pasa al siguiente paso: forwardingTargetForSelector.

Destinatario alternativo

Si el objeto de destino implementa -forwardingTargetForSelector :, Runtime llamará a este método en este momento para darle la oportunidad de reenviar este mensaje a otros objetos.

Un ejemplo de implementación de un receptor alternativo es el siguiente:

Puede ver que reenviamos el método ViewController actual a Person para su ejecución a través de forwardingTargetForSelector. El resultado impreso también demuestra que realizamos con éxito el reenvío.

Reenvío de mensajes completo

Si el mensaje desconocido no se puede procesar en el paso anterior, lo único que se puede hacer es habilitar el mecanismo de reenvío de mensajes completo.

Primero, enviará el mensaje -methodSignatureForSelector: para obtener el parámetro y el tipo de valor de retorno de la función. Si -methodSignatureForSelector: devuelve nil, Runtime emitirá el mensaje -doesNotRecognizeSelector: y el programa se bloqueará en este momento. Si se devuelve una firma de función, Runtime creará un objeto NSInvocation y enviará un mensaje -forwardInvocation: al objeto de destino.

También imprime "Doing foo"

Este es el proceso de tres reenvíos de Runtime. Hablemos de la aplicación práctica de Runtime

 

Cuando las funciones del método integrado del sistema no son suficientes, puede ampliar algunas funciones al método integrado del sistema y mantener las funciones originales. Por ejemplo, quiero saber si la URL actual está vacía. Si la juzgo cada vez, será muy problemático. Si creo una extensión para escribir, no sé cómo se implementa internamente.

1. Se puede utilizar el método de intercambio en tiempo de ejecución.

Dos, también puede agregar métodos dinámicamente

Tres, agregue atributos a la clasificación

 

Cuatro, realización de KVO

La implementación de KVO se basa en el poderoso Runtime de Objective-C. Cuando se observa un objeto A, el mecanismo KVO crea dinámicamente una subclase de la clase actual de objeto A y anula el método de establecimiento de la propiedad observada keyPath para esta nueva subclase. El método setter es entonces responsable de notificar el cambio de estado de las propiedades del objeto observado.

Cinco, reenvío de mensajes (actualización en caliente) para resolver el error (JSPatch)

En cuanto al reenvío de mensajes, el reenvío de mensajes se divide en tres niveles. Podemos implementar la función de reemplazo en cada nivel para lograr el reenvío de mensajes, de modo que no provoque un bloqueo. JSPatch no solo puede realizar el reenvío de mensajes, sino que también puede realizar una serie de funciones de adición y reemplazo de métodos

Seis, realice el archivado y desarchivado automáticos de NSCoding

Descripción del principio: utilice las funciones proporcionadas por el tiempo de ejecución para atravesar todos los atributos del propio modelo y realice operaciones de codificación y decodificación en los atributos.

Método principal: reescriba el método en la clase base de Modelo:

 

Resumen: En toda la operación de Objective-C, todas las llamadas a métodos son el proceso de enviar o reenviar mensajes. Finalmente, la primera imagen se puede convertir aproximadamente en la siguiente para una fácil comprensión

 



Autor: Jaren_lei
enlace: https: //www.jianshu.com/p/45db86af7b60

Supongo que te gusta

Origin blog.csdn.net/wangletiancsdn/article/details/97939901
Recomendado
Clasificación