Unity3D: Modifier le délégué d'application iOS

Je pense que beaucoup dans le processus de développement d'un jeu pour iOS ont dû faire face au fait qu'il devient nécessaire d'utiliser l'une ou l'autre des fonctionnalités natives. Concernant Unity3D, de nombreux problèmes peuvent survenir dans ce problème: pour implémenter une sorte de fonctionnalité, vous devez vous tourner vers des plugins natifs écrits en Objective-C. Quelqu'un en ce moment désespère immédiatement et abandonne l'idée. Quelqu'un recherche des solutions toutes faites dans AssetStore ou dans les forums, en espérant qu'une solution toute faite existe déjà. S'il n'y a pas de solutions toutes faites, alors les plus persistants d'entre nous ne voient pas d'autre moyen que de plonger dans l'abîme de la programmation iOS et de l'interaction Unity3D avec le code Objective-C.

Ceux qui choisissent la dernière voie (bien que, je pense, ils le savent eux-mêmes), seront confrontés à de nombreux problèmes sur cette voie difficile et épineuse:

  • iOS est un écosystème absolument inconnu et isolé, se développant à sa manière. Au minimum, vous devrez consacrer beaucoup de temps à comprendre comment vous pouvez accéder à l'application et où, dans les profondeurs du projet Xcode généré automatiquement, se trouve le code permettant au moteur Unity3D d'interagir avec le composant natif de l'application.
  • Objective-C est un langage de programmation assez distinct et peu semblable. Et lorsqu'il s'agit d'interagir avec le code C ++ de l'application Unity3D, le «dialecte» de ce langage, appelé Objective-C ++, entre en scène. Il y a très peu d'informations sur lui, la plupart étant anciennes et archivistiques.
  • Le protocole d'interaction entre Unity3D et l'application iOS est mal décrit. Vous devez vous fier uniquement aux tutoriels des passionnés de réseau qui écrivent comment développer le plugin natif le plus simple. Dans le même temps, peu de gens abordent des problèmes plus profonds et des problèmes découlant de la nécessité de faire quelque chose de compliqué.

Ceux qui veulent en savoir plus sur les mécanismes d'interaction d'Unity3D avec une application iOS, s'il vous plaît, sous cat.

Afin de clarifier le goulot d'étranglement étanche de l'interaction d'Unity3D avec le code natif, cet article décrit les aspects d'interaction d'un délégué d'application iOS avec le code Unity3D, avec quels outils C ++ et Objective-C il est implémenté, et comment modifier le délégué d'application vous-même. Ces informations peuvent être utiles à la fois pour une meilleure compréhension des mécanismes de liaison Unity3D + iOS et pour une utilisation pratique.

Interaction entre iOS et l'application


En guise d'introduction, regardons comment l'interaction de l'application avec le système est implémentée dans iOS et vice versa. Schématiquement, le lancement d'une application iOS ressemble à ceci:

image

Pour étudier ce mécanisme du point de vue du code, une nouvelle application créée dans Xcode à l'aide du modèle «Single View App» convient.



En choisissant ce modèle, la sortie vous donnera l'application iOS la plus simple qui peut fonctionner sur un appareil ou un émulateur et afficher un écran blanc. Xcode créera utilement un projet dans lequel il n'y aura que 5 fichiers avec le code source (dont 2 étant des fichiers d'en-tête .h) et plusieurs fichiers auxiliaires qui ne nous intéressent pas (composition, configs, icônes).



Voyons de quoi sont responsables les fichiers de code source:

  • ViewController.m / ViewController.h - codes source pas très intéressants pour nous. Étant donné que votre application a une vue (qui n'est pas représentée par du code, mais en utilisant le Storyboard), vous aurez besoin de la classe Controller, qui contrôlera cette vue. En général, de cette façon, Xcode lui-même nous encourage à utiliser le modèle MVC. Le projet qui génère Unity3D n'aura pas ces fichiers source.
  • AppDelegate.m / AppDelegate.h est le délégué de votre application. Point d'intérêt dans l'application où commence le travail du code d'application personnalisé.
  • main.m - le point de départ de l'application. À la manière de toute application C / C ++, il contient la fonction principale, avec laquelle le programme démarre.

Voyons maintenant le code commençant par le fichier main.m :

int main(int argc, char * argv[]) { //1 @autoreleasepool { //2 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); // 3 } } 

Avec la ligne 1, tout est clair et sans explication, passons à la ligne 2. Il indique que le cycle de vie de l'application se produira à l'intérieur du pool Autorelease. L'utilisation du pool de libération automatique nous indique que nous confierons la gestion de la mémoire de l'application à ce pool particulier, c'est-à-dire qu'il traitera les problèmes lorsqu'il sera nécessaire de libérer de la mémoire pour une variable particulière. L'histoire de la gestion de la mémoire sur iOS dépasse le cadre de cette histoire, il est donc inutile de se plonger dans ce sujet. Pour ceux qui sont intéressés par ce sujet, vous pouvez trouver, par exemple, cet article .

Passons à la ligne 3. Il appelle la fonction UIApplicationMain . Les paramètres de démarrage du programme (argc, argv) lui sont transmis. Ensuite, dans cette fonction, il est indiqué quelle classe utiliser comme classe principale de l'application, son instance est créée. Enfin, il est indiqué quelle classe utiliser comme délégué d'application, son instance est créée, les connexions entre l'instance de classe d'application et son délégué sont configurées.

Dans notre exemple, nil est passé en tant que classe qui représentera l'instance d'application - en gros, l'analogue local est nul. En plus de nil, vous pouvez y passer une classe spécifique héritée de UIApplication . Si nil est spécifié, alors UIApplication sera utilisée. Cette classe est un point centralisé pour gérer et coordonner le travail d'une application sur iOS et est un singleton. Avec lui, vous pouvez apprendre presque tout sur l'état actuel de l'application, les notifications, les fenêtres, les événements survenus dans le système lui-même qui affectent cette application et bien plus encore. Cette classe n'hérite presque jamais. Nous nous attarderons sur la création de la classe Application Delegate plus en détail.

Créer un délégué d'application


Une indication de la classe à utiliser en tant que délégué d'application se produit dans un appel de fonction

 NSStringFromClass([AppDelegate class]) 

Analysons cet appel en plusieurs parties.

 [AppDelegate class] 

Cette construction renvoie un objet de la classe AppDelegate (qui est déclarée dans AppDelegate.h / .m) et la fonction NSStringFromClass renvoie le nom de la classe sous forme de chaîne. Nous passons simplement le nom de chaîne de la classe à créer et à utiliser en tant que délégué de la fonction UIApplicationMain. Pour une meilleure compréhension, la ligne 3 du fichier main.m pourrait être remplacée par ce qui suit:

 return UIApplicationMain(argc, argv, nil, @"AppDelegate"); 

Et le résultat de sa mise en œuvre serait identique à la version originale. Apparemment, les développeurs ont décidé d'adopter cette approche afin de ne pas utiliser de constante de chaîne. Avec une approche standard, si vous renommez une classe déléguée, l'analyseur générera immédiatement une erreur. Dans le cas de l'utilisation de la ligne habituelle, le code sera compilé avec succès et vous ne recevrez une erreur qu'en démarrant l'application.

Un mécanisme similaire pour créer une classe, en utilisant uniquement le nom de chaîne de la classe, peut vous rappeler Reflection from C #. Objective-C et son runtime sont beaucoup plus puissants que Reflection en C #. C'est un point assez important dans le contexte de cet article, mais il faudrait beaucoup de temps pour décrire toutes les fonctionnalités. Cependant, nous rencontrerons toujours la «réflexion» dans l'objectif-C ci-dessous. Reste à comprendre le concept de délégué d'application et ses fonctions.

Délégué d'application


Toutes les interactions de l'application avec iOS se produisent dans la classe UIApplication. Cette classe assume de nombreuses responsabilités - informe sur l'origine des événements, l'état de l'application et bien plus encore. Pour la plupart, son rôle est de notifier. Mais quand quelque chose se passe dans le système, nous devrions être en mesure de réagir d'une manière ou d'une autre à ce changement, d'effectuer une sorte de fonctionnalité personnalisée. Si une instance de la classe UIApplication fait cela, cette pratique commencera à ressembler à une approche appelée l' objet divin . Par conséquent, il vaut la peine de penser à libérer cette classe d'une partie de ses responsabilités.

C'est à ces fins que l'écosystème iOS utilise une chose telle qu'un délégué d'application. D'après le nom lui-même, nous pouvons conclure que nous avons affaire à un modèle de conception tel que la délégation . En bref, nous transférons simplement la responsabilité du traitement de la réponse à certains événements de l'application au délégué de l'application. À cet effet, dans notre exemple, la classe AppDelegate a été créée dans laquelle nous pouvons écrire des fonctionnalités personnalisées, tout en laissant la classe UIApplication fonctionner en mode boîte noire. Cette approche peut sembler controversée à quelqu'un en termes de beauté de la conception de l'architecture, mais les auteurs iOS eux-mêmes nous poussent à cette approche et la grande majorité des développeurs (sinon tous) l'utilisent.

Pour vérifier visuellement à quelle fréquence pendant le travail de l'application le délégué d'application reçoit un message particulier, jetez un œil au schéma:

image

Les rectangles jaunes indiquent les appels de l'une ou l'autre méthode déléguée en réponse à certains événements de la vie de l'application (cycle de vie de l'application). Ce diagramme illustre uniquement les événements liés aux modifications de l'état de l'application et n'affiche pas de nombreux autres aspects de la responsabilité du délégué, tels que l'acceptation des notifications ou l'interaction avec les frameworks.

Voici quelques exemples où nous pourrions avoir besoin d'accéder à un délégué d'application de Unity3D:

  1. gestion des notifications push et locales
  2. journalisation des événements de lancement d'application dans l'analytique
  3. détermination de la façon de lancer l'application - «nettoyer» ou quitter l'arrière-plan
  4. comment l'application a été lancée - par tach pour la notification, en utilisant les actions rapides de l'écran d'accueil ou simplement par tach sur incon
  5. interaction avec WatchKit ou HealthKit
  6. ouverture et traitement des URL à partir d'une autre application. Si cette URL s'applique à votre application, vous pouvez la traiter dans votre application au lieu de laisser le système ouvrir cette URL dans un navigateur

Ce n'est pas toute la liste des scénarios. De plus, il convient de noter que le délégué modifie de nombreux systèmes d'analyse et de publicité dans ses plugins natifs.

Comment Unity3D implémente un délégué d'application


Examinons maintenant le projet Xcode généré par Unity3D et découvrons comment le délégué d'application est implémenté dans Unity3D. Lors de la construction pour la plate-forme iOS, Unity3D génère automatiquement un projet Xcode pour vous, qui utilise beaucoup de code standard. Ce code de modèle inclut également le code de délégué d'application. Dans tout projet généré, vous pouvez trouver les fichiers UnityAppController.h et UnityAppController.mm . Ces fichiers contiennent le code de la classe UnityAppController qui nous intéresse.

En fait, Unity3D utilise une version modifiée du modèle «Application vue unique». Dans ce modèle uniquement, Unity3D utilise le délégué d'application non seulement pour gérer les événements iOS, mais aussi pour initialiser le moteur lui-même, préparer les composants graphiques, et bien plus encore. C'est très facile à comprendre si vous regardez la méthode.

 - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions 

dans le code de la classe UnityAppController. Cette méthode est appelée au moment de l'initialisation de l'application, lorsque vous pouvez transférer le contrôle vers votre code personnalisé. Dans cette méthode, par exemple, vous pouvez trouver les lignes suivantes:

 UnityInitApplicationNoGraphics([[[NSBundle mainBundle] bundlePath] UTF8String]); [self selectRenderingAPI]; [UnityRenderingView InitializeForAPI: self.renderingAPI]; _window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds]; _unityView = [self createUnityView]; [DisplayManager Initialize]; _mainDisplay = [DisplayManager Instance].mainDisplay; [_mainDisplay createWithWindow: _window andView: _unityView]; [self createUI]; [self preStartUnity]; 

Sans même entrer dans les détails de ce que font ces défis, vous pouvez deviner qu'ils sont liés à la préparation d'Unity3D pour le travail. Il s'avère que le scénario suivant:

  1. La fonction principale est appelée depuis main.mm
  2. Les classes d'instance de l'application et de son délégué sont créées.
  3. Le délégué d'application prépare et lance le moteur Unity3D
  4. Votre code personnalisé commence à fonctionner. Si vous utilisez il2cpp, votre code est traduit de C # en IL puis en code C ++, qui pénètre directement dans le projet Xcode.

Ce script semble assez simple et logique, mais il entraîne un problème potentiel: comment pouvons-nous modifier le délégué d'application si nous n'avons pas accès au code source lorsque nous travaillons dans Unity3D?

Unity3D affecté pour modifier le délégué d'application


Nous pouvons jeter un œil aux fichiers AppDelegateListener.mm/.h . Ils contiennent des macros qui vous permettent d'enregistrer n'importe quelle classe comme écouteur d'événements pour le délégué d'application. C'est une bonne approche, nous n'avons pas besoin de modifier le code existant, mais simplement d'en ajouter un nouveau. Mais il présente un inconvénient important: tous les événements d'application ne sont pas pris en charge et il n'y a aucun moyen d'obtenir des informations sur le lancement de l'application.

La solution la plus évidente, cependant, inacceptable est de modifier le code source délégué à la main après que Unity3D ait construit le projet Xcode. Le problème avec cette approche est évident - l'option est appropriée si vous faites des assemblages avec vos mains et que vous n'êtes pas confus par la nécessité de modifier le code manuellement après chaque assemblage. Dans le cas de l'utilisation de générateurs (Unity Cloud Build ou toute autre machine de génération), cette option est absolument inacceptable. À ces fins, les développeurs Unity3D nous ont laissé une faille.

Le fichier UnityAppController.h , en plus de déclarer des variables et des méthodes, contient également une définition de macro:

 #define IMPL_APP_CONTROLLER_SUBCLASS(ClassName) ... 

Cette macro permet simplement de remplacer le délégué d'application. Pour ce faire, vous devez suivre quelques étapes simples:

  1. Écrivez votre propre délégué d'application dans Objective-C
  2. Quelque part dans le code source, ajoutez la ligne suivante
     IMPL_APP_CONTROLLER_SUBCLASS(___) 
  3. Mettez cette source dans le dossier Plugins / iOS de votre projet Unity3D

Vous allez maintenant recevoir un projet dans lequel le délégué d'application Unity3D standard sera remplacé par votre projet personnalisé.

Comment fonctionne la macro de remplacement de délégué


Regardons le code source complet de la macro:

 #define IMPL_APP_CONTROLLER_SUBCLASS(ClassName) ... @interface ClassName(OverrideAppDelegate) \ { \ } \ +(void)load; \ @end \ @implementation ClassName(OverrideAppDelegate) \ +(void)load \ { \ extern const char* AppControllerClassName; \ AppControllerClassName = #ClassName; \ } \ @end 

L'utilisation de cette macro dans votre source ajoutera le code décrit dans la macro au corps de votre source au stade de la compilation. Cette macro effectue les opérations suivantes. Tout d'abord, il ajoutera la méthode de chargement à l'interface de votre classe. Une interface dans le contexte d'Objective-C peut être considérée comme un ensemble de champs et de méthodes publics. En C #, une méthode de chargement statique apparaîtra dans votre classe qui ne renvoie rien. Ensuite, l'implémentation de cette méthode de chargement sera ajoutée au code de votre classe. Dans cette méthode, la variable AppControllerClassName sera déclarée, qui est un tableau de type char, puis une valeur sera affectée à cette variable. Cette valeur est le nom de chaîne de votre classe. De toute évidence, ces informations ne sont pas suffisantes pour comprendre le mécanisme de fonctionnement de cette macro, par conséquent, nous devons comprendre ce qu'est cette méthode de «chargement» et pourquoi une variable est déclarée.

La documentation officielle indique que la charge est une méthode spéciale qui est appelée une fois pour chaque classe (en particulier la classe, pas ses instances) au tout début du lancement de l'application, avant même que la fonction principale soit appelée. L'environnement d'exécution Objective-c (runtime) au démarrage de l'application enregistrera toutes les classes qui seront utilisées pendant le fonctionnement de l'application et appellera la méthode de chargement, si elle est implémentée. Il s'avère que même avant le début de tout code dans notre application, la variable AppControllerClassName sera ajoutée à votre classe.

Ensuite, vous pourriez penser: "Et quel est l'intérêt d'avoir cette variable si elle est déclarée dans la méthode et sera supprimée de la mémoire lorsque vous quittez cette méthode?". La réponse à cette question se situe un peu au-delà des limites de l'Objectif-C.

Et où est C ++?


Jetons un autre regard sur la déclaration de cette variable

 extern const char* AppControllerClassName; 

La seule chose qui peut être incompréhensible dans cette déclaration est le modificateur extern. Si vous essayez d'utiliser ce modificateur en Objective-C pur, alors Xcode lancera une erreur. Le fait est que ce modificateur ne fait pas partie d'Objective-C, il est implémenté en C ++. Objective-C peut être décrit assez succinctement en disant qu'il s'agit d'un "langage C avec des classes". C'est une extension du langage C et permet une utilisation illimitée du code C entrecoupé de code Objective-C.

Cependant, pour utiliser extern et d'autres fonctionnalités C ++, vous devez faire un tour - utilisez Objective-C ++. Il n'y a pratiquement aucune information sur ce langage, car c'est juste du code Objective-C qui permet l'insertion de code C ++. Pour que le compilateur considère que certains fichiers source doivent être compilés en Objective-C ++, et non en Objective-C, il vous suffit de changer l'extension de ce fichier de .m en .mm .

Le modificateur extern lui-même est utilisé pour déclarer une variable globale. Plus précisément, pour dire au compilateur «Croyez-moi, une telle variable existe, mais sa mémoire n'a pas été allouée ici, mais dans une autre source. Et elle a aussi une valeur, je vous le garantis. » Ainsi, notre ligne de code crée simplement une variable globale et y stocke le nom de notre classe personnalisée. Il ne reste plus qu'à comprendre où cette variable peut être utilisée.

Retour à la page principale


Nous rappelons ce qui a été dit précédemment - le délégué d'application est créé en spécifiant le nom de classe. Si le délégué a été créé en utilisant la valeur constante [classe myClass] dans le modèle de projet Xcode normal, alors, apparemment, les gars de Unity ont décidé que cette valeur devrait être enveloppée dans une variable. En utilisant la méthode de poke scientifique, nous prenons le projet Xcode généré par Unity3D et allons dans le fichier main.mm.

On y voit du code plus complexe qu'auparavant, une partie de ce code est manquante car inutile:

 // WARNING: this MUST be c decl (NSString ctor will be called after +load, so we cant really change its value) const char* AppControllerClassName = "UnityAppController"; int main(int argc, char* argv[]) { ... UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String: AppControllerClassName]); } return 0; } 

Ici, nous voyons la déclaration de cette très variable, et la création du délégué d'application avec son aide.
Si nous avons créé un délégué personnalisé, la variable nécessaire existe et compte déjà - le nom de notre classe. La déclaration et l'initialisation de la variable avant la fonction principale garantit qu'elle a une valeur par défaut - UnityAppController.

Maintenant, avec cette décision, tout devrait être très clair.

Problème de macro


Bien sûr, pour la grande majorité des situations, l'utilisation de cette macro est une excellente solution. Mais il convient de noter qu'il y a un gros écueil: vous ne pouvez pas avoir plus d'un délégué personnalisé. Cela se produit car si 2 classes ou plus utilisent la macro IMPL_APP_CONTROLLER_SUBCLASS (ClassName), alors pour la première d'entre elles, la valeur de la variable dont nous avons besoin sera affectée et les autres affectations seront ignorées. Et cette variable est une chaîne, c'est-à-dire qu'elle ne peut pas être affectée à plus d'une valeur.

Vous pourriez penser que ce problème est dégénéré et peu probable dans la pratique. Mais, cet article ne se serait pas produit si un tel problème ne s'était pas vraiment produit, et même dans des circonstances très étranges. La situation peut être la suivante. Vous avez un projet dans lequel vous utilisez de nombreux services d'analyse et de publicité. Beaucoup de ces services ont des composants Objective-C. Ils sont dans votre projet depuis longtemps et vous ne connaissez pas les problèmes avec eux. Ici, vous devez écrire un délégué personnalisé. Vous utilisez une macro magique conçue pour vous éviter des problèmes, construire un projet et obtenir un rapport sur le succès de l'assemblage. Exécutez le projet sur l'appareil et votre fonctionnalité ne fonctionne pas et vous ne recevez pas une seule erreur.

Et le problème peut être que l'un des plugins publicitaires ou d'analyse utilise la même macro. Par exemple, dans le plugin d' AppsFlyer, cette macro est utilisée.

Quelle est la valeur de la variable externe dans le cas de déclarations multiples?


Il est intéressant de savoir si la même variable externe est déclarée dans plusieurs fichiers, et ils sont initialisés à la manière de notre macro (dans la méthode de chargement), alors comment comprendre la valeur que prendra la variable? Pour comprendre le modèle, une application de test simple a été créée, dont le code peut être trouvé ici .

L'essence de l'application est simple. Il existe 2 classes A et B, dans les deux classes, la variable externe AexternVar est déclarée, une valeur spécifique lui est affectée. Les valeurs de la variable dans les classes sont définies différemment. Dans la fonction principale, la valeur de cette variable est enregistrée. Il a été constaté expérimentalement que la valeur de la variable dépend de l'ordre dans lequel les sources sont ajoutées au projet. L'ordre dans lequel le runtime Objective-C enregistre les classes pendant l'exécution de l'application en dépend. Si vous souhaitez répéter l'expérience, ouvrez le projet et sélectionnez l'onglet Build Phases dans les paramètres du projet. Étant donné que le projet est test et petit, il ne dispose que de 8 codes sources. Tous sont présents dans l'onglet Générer des phases de la liste Compiler les sources.



Si dans cette liste la source de la classe A est supérieure à la source de la classe B, alors la variable prendra une valeur de la classe B. Sinon, la variable prendra une valeur de la classe A.

Imaginez simplement combien de problèmes cela peut théoriquement causer est une petite nuance. Surtout si le projet est énorme, généré automatiquement et que vous ne savez pas dans quelles classes une telle variable est déclarée.

Résolution de problèmes


Plus tôt dans l'article, il a été dit qu'Objective-C donnera une longueur d'avance à la réflexion C #. Plus précisément, pour résoudre notre problème, vous pouvez utiliser le mécanisme appelé méthode Swizzling . L'essence de ce mécanisme est que nous avons la possibilité de remplacer l'implémentation d'une méthode d'une classe par une autre lors de l'application. Ainsi, nous pouvons remplacer la méthode d'intérêt dans UnityAppController par une méthode personnalisée. Nous prenons l'implémentation existante et complétons le code dont nous avons besoin. Nous écrivons du code qui remplace l'implémentation existante de la méthode par celle dont nous avons besoin. Pendant le travail de l'application, le délégué utilisant la macro fonctionnera comme auparavant, appelant l'implémentation de base d'UnityAppController, et là, notre méthode personnalisée entrera en jeu et nous obtiendrons le résultat souhaité. Cette approche est bien écrite et illustrée dans cet article . Avec cette technique, nous pouvons créer une classe auxiliaire - un analogue d'un délégué personnalisé.Dans cette classe, nous écrirons tout le code personnalisé, faisant de la classe personnalisée une sorte de Wrapper pour appeler la fonctionnalité des autres classes. Cette approche fonctionnera, mais elle est extrêmement implicite en raison du fait qu'il est difficile de savoir où la méthode est remplacée et quelles conséquences elle entraînera.

Une autre solution au problème


L'aspect principal du problème qui s'est produit est qu'il y a beaucoup de délégués personnalisés, ou vous ne pouvez en avoir qu'un, ou le remplacer partiellement par un second. Dans le même temps, il n'y a aucun moyen de s'assurer que le code des délégués personnalisés ne se glisse pas dans différents fichiers source. Il s'avère que la situation peut être considérée comme une référence lorsqu'il n'y a qu'un seul délégué dans l'application, vous devez pouvoir créer autant de classes personnalisées que vous le souhaitez, tandis qu'aucune de ces classes n'utilise la macro pour éviter les problèmes.

La chose est petite, il reste à déterminer comment cela peut être fait en utilisant Unity3D, tout en laissant la possibilité de construire un projet à l'aide d'une machine de construction. L'algorithme de solution est le suivant:

  1. Nous écrivons des délégués personnalisés dans la quantité requise, divisant la logique des plugins en différentes classes, observant les principes de SOLID et ne recourant pas à la sophistication.
  2. UnityAppController XCode . UnityAppController .
  3. UnityAppController Unity .
  4. XCode UnityAppController ,

L'élément le plus difficile de cette liste est sans aucun doute le dernier. Cependant, cette fonctionnalité peut être implémentée dans Unity3D à l'aide du script de génération post-processus. Un tel script a été écrit une belle nuit, vous pouvez le regarder sur GitHub .

Ce post-processus est assez simple à utiliser, choisissez-le dans un projet Unity. Regardez dans la fenêtre Inspecteur et voyez là un champ appelé NewDelegateFile. Faites glisser et déposez votre UnityAppController modifié dans ce champ et enregistrez.



Lors de la construction d'un projet iOS, le délégué standard sera remplacé par un délégué modifié et aucune intervention manuelle n'est requise. Désormais, lorsque vous ajoutez de nouveaux délégués personnalisés au projet, il vous suffit de modifier l'option UnityAppController qui traîne dans votre projet Unity.

PS


Merci à tous ceux qui sont arrivés à la fin, l'article s'est avéré extrêmement long. J'espère que les informations peintes vous seront utiles.

Source: https://habr.com/ru/post/fr430118/


All Articles