Runtime et transfert de messages sous iOS

Au début des années 80, Xiao Li et Xiao Wang formaient un couple de longue distance. Sous la direction de la corne de la réforme, Xiao Wang a résolument choisi une ville du sud pour se battre. À cette époque, il n'y avait pas de téléphone portable. Comptez sur l'écriture de lettres. Cependant, comme Xiao Wang voyage fréquemment, son adresse résidentielle changera fréquemment. Par conséquent, chaque fois que Xiao Li envoie une réponse à Xiao Wang, Xiao Wang peut ne pas la recevoir en raison du changement d'adresse. Plus tard, ils ont pensé à un bon moyen de résoudre ce problème. La méthode spécifique est la suivante:

Transfert de messages dans les années 80

 

En fait, l'image ci-dessus peut essentiellement exprimer la fonction de Runtime dans iOS et le mécanisme de transfert de message d'iOS. La caractéristique de Runtime est principalement la livraison de messages (méthodes). Si le message (méthode) ne peut pas être trouvé dans l'objet, il sera retransmis. Comment est-ce fait? Nous explorons le mécanisme de mise en œuvre de Runtime à partir des aspects suivants.

Introduction à l'exécution

Objective-C étend le langage C et ajoute des fonctionnalités orientées objet et un mécanisme de messagerie de type Smalltalk. Le cœur de cette extension est une bibliothèque Runtime écrite en C et en langage compilé. C'est la pierre angulaire des mécanismes orientés objet et dynamiques d'Objective-C.

Objective-C est un langage dynamique, ce qui signifie qu'il nécessite non seulement un compilateur, mais aussi un système d'exécution pour créer dynamiquement des classes et des objets, et effectuer le passage et le transfert de messages. Comprendre le mécanisme d'exécution d'Objective-C peut nous aider à mieux comprendre le langage, et le cas échéant, nous pouvons étendre le langage pour résoudre certains problèmes de conception ou techniques dans le projet à partir du niveau du système. Pour comprendre Runtime, nous devons d'abord comprendre son cœur de messagerie (Messagerie).

Pour devenir un fichier exécutable, un langage de programmation de haut niveau doit être compilé en langage assembleur puis assemblé en langage machine. Le langage machine est également le seul langage que l'ordinateur peut reconnaître, mais OC ne peut pas être directement compilé en langage assembleur, mais doit être transcrit en langage C. pur. Le langage est ensuite compilé et assemblé, et la transition du langage OC au langage C est réalisée au runtime. Cependant, nous utilisons OC pour le développement orienté objet, et le langage C est un développement plus orienté processus, ce qui nécessite la conversion de classes orientées objet en structures orientées processus.

Ce qui précède sont toutes des interprétations de documents officiels, qui sont un peu obscures et ennuyeuses, alors utilisons le code pour expliquer en détail.

Messagerie d'exécution

Pour une méthode objet  [obj test] , le compilateur la convertit en un message à envoyer objc_msgSend (obj, test) . Le processus exécuté au Runtime est comme ceci:

1. Tout d'abord, trouvez sa classe via le pointeur isa de obj ;

2. Trouvez le test dans la liste des méthodes de la classe ;

3. S'il n'y a pas de test dans la classe , continuez à rechercher sa superclasse ;

4. Une fois le test de fonction trouvé , exécutez son implémentation IMP

Bien sûr, en raison de problèmes d'efficacité, il n'est pas raisonnable de parcourir la liste objc_method_list une fois pour chaque message. Par conséquent, il est nécessaire de mettre en cache les fonctions fréquemment appelées pour améliorer l'efficacité de la requête de fonction. C'est ce que fait objc_cache, un autre membre important de objc_class, après avoir trouvé le test, enregistrer le nom_méthode du test comme clé et method_imp comme valeur. Lorsque le message de test est de nouveau reçu, il peut être trouvé directement dans le cache pour éviter de parcourir la liste objc_method_list. À partir du code source précédent, vous pouvez voir que objc_cache existe dans la structure objc_class.

La méthode de objec_msgSend:

OBJC_EXPORTidobjc_msgSend (idself, SEL activé, ...)

Jetons un coup d'œil aux structures des objets, classes et méthodes:

Objet de classe (objc_class)

La classe Objective-C est représentée par le type Class, qui est en fait un pointeur vers la structure objc_class

La structure struct objc_class définit de nombreuses variables. La structure enregistre le pointeur vers la classe parente, le nom de la classe, la version, la taille de l'instance, la liste des variables d'instance, la liste des méthodes, le cache, la liste des protocoles de conformité, etc. On voit que l'objet de classe est une structure struct objc_class, cette structure Les données stockées dans le corps sont des métadonnées.

Instance (objc_object)

 

Les métadonnées de l'objet de classe stockent des informations sur la façon de créer une instance, qui est créée à partir de la structure pointée par le pointeur isa. Le pointeur isa de l'objet de classe pointe vers la métaclasse.

Toutes les informations nécessaires pour créer des objets de classe et des méthodes de classe sont enregistrées dans la métaclasse, la structure entière doit donc être comme indiqué dans la figure suivante:

Objet d'instance, objet de classe et diagramme de métaclasse

Le pointeur isa de la structure struct objc_object pointe vers l'objet de classe;

Le pointeur isa de l'objet de classe pointe vers la métaclasse;

Le pointeur super_class pointe vers l'objet de classe de la classe parent;

Le pointeur super_class de la métaclasse pointe vers la métaclasse de la classe parent;

Cela ressemble à un virelangue, donc il peut être représenté par une carte de Dieu sur Internet:

Figure 6 Boucle auto-fermée d'objet d'instance, d'objet de classe et de métaclasse

D'après la figure ci-dessus, nous pouvons voir que l'ensemble du système constitue une boucle auto-fermée.S'il est hérité de NSObject, la classe Root dans la figure ci-dessus est NSObject.

 

c1 est une classe obtenue via un objet instance. L'objet instance peut obtenir son objet classe. Le nom de classe représente l'objet classe lorsqu'il est le destinataire du message. Par conséquent, l'objet classe obtient lui-même la classe.

Si nous voulons obtenir l'objet du pointeur ISA, nous pouvons utiliser les deux fonctions suivantes

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 est utilisé pour déterminer si l'objet Class est une métaclasse, et object_getClass est utilisé pour obtenir l'objet pointé par le pointeur isa de l'objet.

 

Il peut être vu à partir du code que la classe obtenue par un objet instance via la méthode de classe est l'objet de classe pointé par son pointeur isa, tandis que l'objet de classe n'est pas une métaclasse et que l'objet pointé par le pointeur isa de l'objet de classe est une métaclasse.

À propos de la partie Runtime, résumons: tout d'abord, l'objet d'instance est une structure. Cette structure n'a qu'une seule variable membre, pointant vers l'objet de classe qui l'a construite. Cet objet de classe stocke toutes les informations nécessaires à l'objet d'instance, y compris les variables d'instance Les méthodes d'instance, etc., tandis que les objets de classe sont créés via des métaclasses, les variables de classe et les méthodes de classe sont stockées dans la métaclasse, ce qui explique parfaitement comment l'ensemble de la classe et de l'instance sont mappés à la structure. Donc, comprendre Runtime, c'est comprendre le stockage de données d'iOS et la relation et la fonction entre ses classes, instances, objets de classe et métaclasses au moment de l'exécution.

Mécanisme de transfert de message

 Ce qui précède a mentionné beaucoup de compréhension et de concepts de base de Runtime. Qu'est-ce que cela a à voir avec le transfert de message et comment l'utiliser? Il s'agit du mécanisme de transfert de messages iOS.

En dernière analyse, tous les appels de méthode en Objective-C envoient essentiellement des messages aux objets.

1. Créez une méthode dans la classe- (void) todoSomething;

2. Le système iOS crée un numéro pour cette méthode, à savoir: SEL (todoSomething) et l'ajoute à la liste des méthodes. (Le sélecteur est une instance de SEL, qui est différente de IMP, qui est un pointeur vers l'adresse mémoire du programme d'implémentation final)

3. Lors de l'appel de cette méthode: [Object todoSomething]: Le système accède à la liste des méthodes pour insérer ce numéro de méthode et l'exécute une fois trouvé.

Remarque: Lorsque nous écrivons du code C, nous utilisons souvent la surcharge de fonctions, c'est-à-dire que le nom de la fonction est le même mais que les paramètres sont différents, mais ce n'est pas faisable en Objective-C, car le sélecteur ne se souvient que du nom de la méthode et pas de paramètres. Il n'y a donc aucun moyen de faire la distinction entre les différentes méthodes.

Ainsi, si une méthode est appelée, le message sera envoyé une fois et la liste des méthodes sera recherchée dans l'objet de classe associé. Si elle n'est pas trouvée, elle recherchera l'arborescence d'héritage jusqu'à ce que la racine de l'arborescence d'héritage (généralement NSObject) soit trouvée. S'il échoue et que le transfert de message échoue, exécutez la méthode doesNotRecognizeSelector: et signalez une erreur de sélecteur non reconnue. Alors, qu'est-ce que le transfert de message exactement? Ensuite, nous présenterons les trois dernières opportunités une par une.

1. Analyse de méthode dynamique

Objective-C appellera + resolutionInstanceMethod: ou + resolutionClassMethod: au moment de l'exécution pour vous donner la possibilité de fournir une implémentation de fonction. Si vous ajoutez une fonction et renvoyez OUI, le système d'exécution redémarrera le processus d'envoi de message. Exemple comme indiqué ci-dessous

Imprimé "Doing foo"

On peut voir que bien que la fonction foo: ne soit pas implémentée, nous ajoutons dynamiquement la fonction fooMethod via class_addMethod et exécutons l'IMP de la fonction fooMethod. À en juger par les résultats imprimés, cela a été réalisé avec succès.

Si la méthode de résolution renvoie NON, le moteur d'exécution passe à l'étape suivante: forwardingTargetForSelector.

Autre destinataire

Si l'objet cible implémente -forwardingTargetForSelector :, Runtime appellera cette méthode à ce moment pour vous donner la possibilité de transmettre ce message à d'autres objets.

Voici un exemple de mise en œuvre d'un autre récepteur:

Vous pouvez voir que nous avons transféré la méthode ViewController actuelle à Person pour exécution via forwardingTargetForSelector. Le résultat imprimé prouve également que nous avons réalisé avec succès l'expédition.

Transfert de message complet

Si le message inconnu ne peut pas être traité à l'étape précédente, la seule chose à faire est d'activer le mécanisme de transfert de message complet.

Tout d'abord, il enverra le message -methodSignatureForSelector: pour obtenir le paramètre et le type de valeur de retour de la fonction. Si -methodSignatureForSelector: retourne nil, Runtime émettra -doesNotRecognizeSelector: message et le programme se bloquera à ce moment. Si une signature de fonction est renvoyée, Runtime crée un objet NSInvocation et envoie un message -forwardInvocation: à l'objet cible.

Imprime également "Doing foo"

Il s'agit des trois processus de transfert de Runtime. Parlons de l'application pratique de Runtime

 

Lorsque les fonctions de méthode intégrées du système ne suffisent pas, vous pouvez étendre certaines fonctions à la méthode intégrée du système et conserver les fonctions d'origine. Par exemple, je veux savoir si l'URL actuelle est vide. Si je la juge à chaque fois, ce sera très gênant. Si je crée une extension à écrire, je ne sais pas comment elle est implémentée en interne.

1. La méthode d'échange d'exécution peut être utilisée.

Deuxièmement, vous pouvez également ajouter des méthodes dynamiquement

Troisièmement, ajoutez des attributs à la classification

 

Quatre, réalisation KVO

L'implémentation de KVO repose sur le puissant Runtime d'Objective-C.Lorsqu'un objet A est observé, le mécanisme KVO crée dynamiquement une sous-classe de la classe actuelle de l'objet A et remplace la méthode setter de la propriété observée keyPath pour cette nouvelle sous-classe. La méthode setter est alors chargée de notifier le statut modifié des propriétés de l'objet observé.

Cinq, transfert de message (mise à jour à chaud) pour résoudre le bogue (JSPatch)

Concernant le transfert de message, le transfert de message est divisé en trois niveaux: nous pouvons implémenter la fonction de remplacement à chaque niveau pour réaliser le transfert de message, afin de ne pas provoquer de crash. JSPatch peut non seulement réaliser le transfert de message, mais également réaliser une série de fonctions d'ajout et de remplacement de méthode.

Sixièmement, réalisez l'archivage automatique et la désarchivage automatique de NSCoding

Description du principe: utilisez les fonctions fournies par le runtime pour parcourir tous les attributs du modèle lui-même et effectuer des opérations de codage et de décodage sur les attributs.

Méthode principale: réécrire la méthode dans la classe de base de Model:

 

Résumé: Dans toute l'opération Objective-C, tous les appels de méthode sont le processus d'envoi ou de transfert de messages. Enfin, la première image peut être grossièrement transformée en la suivante, ce qui est facile à comprendre

 



Auteur: Jaren_lei
lien: https: //www.jianshu.com/p/45db86af7b60

Je suppose que tu aimes

Origine blog.csdn.net/wangletiancsdn/article/details/97939901
conseillé
Classement