Unity3D: Ändern Sie den iOS App Delegate

Ich denke, dass viele bei der Entwicklung eines Spiels für iOS mit der Tatsache konfrontiert werden mussten, dass es notwendig wird, die eine oder andere native Funktionalität zu verwenden. In Bezug auf Unity3D können in diesem Problem viele Probleme auftreten: Um eine Funktion zu implementieren, müssen Sie sich auf native Plugins konzentrieren, die in Objective-C geschrieben wurden. Jemand in diesem Moment verzweifelt sofort und gibt die Idee auf. Jemand sucht im AssetStore oder in Foren nach vorgefertigten Lösungen, in der Hoffnung, dass es bereits eine vorgefertigte Lösung gibt. Wenn es keine vorgefertigten Lösungen gibt, sehen die hartnäckigsten von uns keinen anderen Weg, als in den Abgrund der iOS-Programmierung und der Unity3D-Interaktion mit Objective-C-Code einzutauchen.

Diejenigen, die den letzten Weg wählen (obwohl sie, wie ich glaube, selbst wissen), werden auf diesem schwierigen und heiklen Weg mit vielen Problemen konfrontiert sein:

  • iOS ist ein absolut unbekanntes und isoliertes Ökosystem, das sich auf seine eigene Weise entwickelt. Zumindest müssen Sie viel Zeit aufwenden, um zu verstehen, wie Sie zur Anwendung gelangen können und wo sich in den Tiefen des automatisch generierten Xcode-Projekts der Code für die Unity3D-Engine befindet, um mit der nativen Komponente der Anwendung zu interagieren.
  • Objective-C ist eine ziemlich separate und wenig ähnliche Programmiersprache. Und wenn es um die Interaktion mit dem C ++ - Code der Unity3D-Anwendung geht, tritt der „Dialekt“ dieser Sprache, Objective-C ++ genannt, in die Szene ein. Es gibt sehr wenig Informationen über ihn, das meiste davon ist uralt und archiviert.
  • Das Interaktionsprotokoll zwischen Unity3D und iOS-Anwendung ist schlecht beschrieben. Sie sollten sich ausschließlich auf die Tutorials von Netzwerkbegeisterten verlassen, die schreiben, wie das einfachste native Plugin entwickelt wird. Gleichzeitig berühren nur wenige Menschen tiefere Probleme und Probleme, die sich aus der Notwendigkeit ergeben, etwas Kompliziertes zu tun.

Diejenigen, die mehr über die Mechanismen der Interaktion von Unity3D mit einer iOS-Anwendung erfahren möchten, finden Sie unter cat.

In diesem Artikel werden die Interaktionsaspekte eines iOS-Anwendungsdelegierten mit Unity3D-Code beschrieben, mit welchen C ++ - und Objective-C-Tools er implementiert ist, und wie Sie den Anwendungsdelegierten selbst ändern können, um den düsteren Engpass bei der Interaktion von Unity3D mit nativem Code zu klären. Diese Informationen können sowohl zum besseren Verständnis der Unity3D + iOS-Verknüpfungsmechanismen als auch für den praktischen Gebrauch hilfreich sein.

Interaktion zwischen iOS und Anwendung


Schauen wir uns als Einführung an, wie die Interaktion der Anwendung mit dem System in iOS implementiert ist und umgekehrt. Schematisch sieht der Start einer iOS-Anwendung folgendermaßen aus:

Bild

Um diesen Mechanismus unter dem Gesichtspunkt des Codes zu untersuchen, ist eine neue Anwendung geeignet, die in Xcode mithilfe der Vorlage „Single View App“ erstellt wurde.



Wenn Sie diese Vorlage auswählen, erhalten Sie mit der Ausgabe die einfachste iOS-Anwendung, die auf einem Gerät oder Emulator ausgeführt werden kann und einen weißen Bildschirm anzeigt. Xcode erstellt hilfreich ein Projekt, in dem es nur 5 Dateien mit Quellcode (von denen 2 Header-H-Dateien sind) und mehrere Hilfsdateien gibt, die für uns nicht interessant sind (Satz, Konfigurationen, Symbole).



Mal sehen, wofür die Quellcodedateien verantwortlich sind:

  • ViewController.m / ViewController.h - für uns nicht sehr interessante Quellcodes. Da Ihre Anwendung über eine Ansicht verfügt (die nicht durch Code dargestellt wird, sondern das Storyboard verwendet), benötigen Sie die Controller-Klasse, die diese Ansicht steuert. Auf diese Weise ermutigt uns Xcode im Allgemeinen, das MVC-Muster zu verwenden. Das Projekt, das Unity3D generiert, verfügt nicht über diese Quelldateien.
  • AppDelegate.m / AppDelegate.h ist der Delegat Ihrer Anwendung. Der Punkt von Interesse in der Anwendung, an dem die Arbeit des benutzerdefinierten Anwendungscodes beginnt.
  • main.m - der Startpunkt der Anwendung. In der Art einer C / C ++ - Anwendung enthält es die Hauptfunktion, mit der das Programm startet.

Lassen Sie uns nun den Code sehen, der mit der Datei main.m beginnt :

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

Mit Zeile 1 ist alles klar und ohne Erklärung fahren wir mit Zeile 2 fort. Dies zeigt an, dass der Anwendungslebenszyklus innerhalb des Autorelease-Pools stattfinden wird. Die Verwendung des Autorelease-Pools teilt uns mit, dass wir die Speicherverwaltung der Anwendung diesem bestimmten Pool anvertrauen, dh Probleme behandeln, wenn Speicher für eine bestimmte Variable freigegeben werden muss. Die Geschichte über die Speicherverwaltung unter iOS geht über den Rahmen dieser Geschichte hinaus, sodass es keinen Sinn macht, sich mit diesem Thema zu befassen. Für diejenigen, die sich für dieses Thema interessieren, finden Sie beispielsweise diesen Artikel .

Fahren wir mit Zeile 3 fort. Sie ruft die UIApplicationMain- Funktion auf. Die Programmstartparameter (argc, argv) werden an sie übergeben. In dieser Funktion wird dann angegeben, welche Klasse als Hauptklasse der Anwendung verwendet werden soll, deren Instanz erstellt wird. Und schließlich wird angegeben, welche Klasse als Anwendungsdelegat verwendet werden soll, ihre Instanz erstellt wird und die Verbindungen zwischen der Anwendungsklasseninstanz und ihrem Delegaten konfiguriert werden.

In unserem Beispiel wird nil als die Klasse übergeben, die die Anwendungsinstanz darstellt - grob gesagt ist das lokale Analogon null. Zusätzlich zu nil können Sie dort eine bestimmte Klasse übergeben, die von UIApplication geerbt wurde. Wenn nil angegeben ist, wird UIApplication verwendet. Diese Klasse ist ein zentraler Punkt für die Verwaltung und Koordination der Arbeit einer Anwendung unter iOS und ein Singleton. Damit können Sie fast alles über den aktuellen Status der Anwendung, Benachrichtigungen, Fenster, Ereignisse im System selbst, die sich auf diese Anwendung auswirken, und vieles mehr erfahren. Diese Klasse erbt fast nie. Wir werden uns eingehender mit der Erstellung der Application Delegate-Klasse befassen.

Anwendungsdelegierten erstellen


In einem Funktionsaufruf wird angegeben, welche Klasse als Anwendungsdelegat verwendet werden soll

 NSStringFromClass([AppDelegate class]) 

Lassen Sie uns diesen Aufruf in Teilen analysieren.

 [AppDelegate class] 

Dieses Konstrukt gibt ein Objekt der AppDelegate-Klasse zurück (das in AppDelegate.h / .m deklariert ist), und die NSStringFromClass- Funktion gibt den Klassennamen als Zeichenfolge zurück. Wir übergeben einfach den Zeichenfolgennamen der Klasse, die erstellt und als Delegat an die UIApplicationMain-Funktion verwendet werden soll. Zum besseren Verständnis könnte Zeile 3 in der Datei main.m durch Folgendes ersetzt werden:

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

Und das Ergebnis seiner Implementierung wäre identisch mit der Originalversion. Anscheinend haben sich die Entwickler für diesen Ansatz entschieden, um keine String-Konstante zu verwenden. Bei einem Standardansatz gibt der Parser beim Umbenennen einer Delegatenklasse sofort einen Fehler aus. Bei Verwendung der üblichen Zeile wird der Code erfolgreich kompiliert und Sie erhalten nur beim Starten der Anwendung eine Fehlermeldung.

Ein ähnlicher Mechanismus zum Erstellen einer Klasse, bei dem nur der Zeichenfolgenname der Klasse verwendet wird, erinnert Sie möglicherweise an die Reflexion von C #. Objective-C und seine Laufzeit sind in C # viel leistungsfähiger als Reflection. Dies ist ein ziemlich wichtiger Punkt im Kontext dieses Artikels, aber es würde viel Zeit in Anspruch nehmen, alle Funktionen zu beschreiben. Wir werden uns jedoch weiterhin mit „Reflexion“ in Ziel C unten treffen. Es bleibt das Konzept des Anwendungsdelegierten und seine Funktionen zu verstehen.

Anwendungsdelegierter


Die gesamte Interaktion der Anwendung mit iOS erfolgt in der UIApplication-Klasse. Diese Klasse übernimmt viele Aufgaben - informiert über den Ursprung von Ereignissen, den Status der Anwendung und vieles mehr. Zum größten Teil ist seine Rolle die Benachrichtigung. Aber wenn etwas im System passiert, sollten wir in der Lage sein, auf diese Änderung irgendwie zu reagieren und eine Art benutzerdefinierte Funktionalität auszuführen. Wenn eine Instanz der UIApplication-Klasse dies tut, ähnelt diese Praxis einem Ansatz, der als göttliches Objekt bezeichnet wird . Daher lohnt es sich, darüber nachzudenken, diese Klasse von einem Teil ihrer Verantwortung zu befreien.

Zu diesem Zweck verwendet das iOS-Ökosystem so etwas wie einen Anwendungsdelegierten. Aus dem Namen selbst können wir schließen, dass es sich um ein Entwurfsmuster wie Delegation handelt . Kurz gesagt, wir übertragen einfach die Verantwortung für die Verarbeitung der Antwort auf bestimmte Ereignisse der Anwendung auf den Anwendungsdelegierten. Zu diesem Zweck wurde in unserem Beispiel die AppDelegate-Klasse erstellt, in die wir benutzerdefinierte Funktionen schreiben können, während die UIApplication-Klasse im Black-Box-Modus arbeiten kann. Dieser Ansatz mag jemandem in Bezug auf die Schönheit des Architekturdesigns kontrovers erscheinen, aber die iOS-Autoren selbst drängen uns zu diesem Ansatz, und die überwiegende Mehrheit der Entwickler (wenn nicht alle) verwendet ihn.

Sehen Sie sich das folgende Diagramm an, um visuell zu überprüfen, wie oft der Anwendungsdelegierte während der Arbeit der Anwendung eine bestimmte Nachricht erhält:

Bild

Die gelben Rechtecke zeigen die Aufrufe der einen oder anderen Delegatenmethode als Reaktion auf bestimmte Ereignisse im Leben der Anwendung an (Anwendungslebenszyklus). Dieses Diagramm zeigt nur Ereignisse im Zusammenhang mit Änderungen im Status der Anwendung und spiegelt nicht viele andere Aspekte der Verantwortung des Delegierten wider, z. B. das Akzeptieren von Benachrichtigungen oder die Interaktion mit Frameworks.

Hier einige Beispiele, bei denen wir möglicherweise Zugriff auf einen Anwendungsdelegierten von Unity3D benötigen:

  1. Umgang mit Push- und lokalen Benachrichtigungen
  2. Protokollieren von Anwendungsstartereignissen in Analytics
  3. Festlegen, wie die Anwendung gestartet werden soll - "bereinigen" oder Hintergrund beenden
  4. wie die Anwendung gestartet wurde - per Tach zur Benachrichtigung, mithilfe von Schnellaktionen auf dem Startbildschirm oder einfach per Tach bei Incon
  5. Interaktion mit WatchKit oder HealthKit
  6. Öffnen und Verarbeiten von URLs aus einer anderen Anwendung. Wenn diese URL für Ihre Anwendung gilt, können Sie sie in Ihrer Anwendung verarbeiten, anstatt das System diese URL in einem Browser öffnen zu lassen

Dies ist nicht die gesamte Liste der Szenarien. Darüber hinaus ist anzumerken, dass der Delegierte viele Analyse- und Werbesysteme in seinen nativen Plugins modifiziert.

Wie Unity3D einen Anwendungsdelegierten implementiert


Schauen wir uns nun das von Unity3D generierte Xcode-Projekt an und finden Sie heraus, wie der Anwendungsdelegierte in Unity3D implementiert ist. Beim Erstellen für die iOS-Plattform generiert Unity3D automatisch ein Xcode-Projekt für Sie, das viel Boilerplate-Code verwendet. Dieser Vorlagencode enthält auch den Anwendungsdelegiertencode. In jedem generierten Projekt finden Sie die Dateien UnityAppController.h und UnityAppController.mm . Diese Dateien enthalten den Code der UnityAppController-Klasse, der uns interessiert.

Tatsächlich verwendet Unity3D eine modifizierte Version der Vorlage "Single View Application". Nur in dieser Vorlage verwendet Unity3D den Anwendungsdelegierten nicht nur zum Behandeln von iOS-Ereignissen, sondern auch zum Initialisieren der Engine selbst, zum Vorbereiten von Grafikkomponenten und vielem mehr. Dies ist sehr leicht zu verstehen, wenn Sie sich die Methode ansehen.

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

im Code der UnityAppController-Klasse. Diese Methode wird zum Zeitpunkt der Anwendungsinitialisierung aufgerufen, wenn Sie die Steuerung auf Ihren benutzerdefinierten Code übertragen können. In dieser Methode finden Sie beispielsweise die folgenden Zeilen:

 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]; 

Ohne auf die Details dieser Herausforderungen einzugehen, können Sie davon ausgehen, dass sie mit der Vorbereitung von Unity3D auf die Arbeit zusammenhängen. Es stellt sich das folgende Szenario heraus:

  1. Die Hauptfunktion wird von main.mm aufgerufen
  2. Instanzklassen der Anwendung und ihres Delegaten werden erstellt.
  3. Der Anwendungsdelegierte bereitet die Unity3D-Engine vor und startet sie
  4. Ihr benutzerdefinierter Code funktioniert. Wenn Sie il2cpp verwenden, wird Ihr Code von C # nach IL und dann in C ++ - Code übersetzt, der direkt in das Xcode-Projekt gelangt.

Dieses Skript klingt ziemlich einfach und logisch, bringt jedoch ein potenzielles Problem mit sich: Wie können wir den Anwendungsdelegierten ändern, wenn wir bei der Arbeit in Unity3D keinen Zugriff auf den Quellcode haben?

Betroffene Unity3D zum Ändern des Anwendungsdelegierten


Wir können uns die Dateien AppDelegateListener.mm/.h ansehen . Sie enthalten Makros, mit denen Sie jede Klasse als Ereignis-Listener für den Anwendungsdelegierten registrieren können. Dies ist ein guter Ansatz. Wir müssen den vorhandenen Code nicht ändern, sondern nur einen neuen hinzufügen. Dies hat jedoch einen erheblichen Nachteil: Es werden nicht alle Anwendungsereignisse unterstützt, und es gibt keine Möglichkeit, Informationen zum Anwendungsstart abzurufen.

Der offensichtlichste, jedoch nicht akzeptable Ausweg besteht darin, den delegierten Quellcode von Hand zu ändern, nachdem Unity3D das Xcode-Projekt erstellt hat. Das Problem bei diesem Ansatz liegt auf der Hand - die Option ist geeignet, wenn Sie Baugruppen mit Ihren Händen erstellen und Sie nicht durch die Notwendigkeit verwirrt sind, den Code nach jeder Baugruppe manuell zu ändern. Bei Verwendung von Buildern (Unity Cloud Build oder einer anderen Build-Maschine) ist diese Option absolut nicht akzeptabel. Für diese Zwecke haben uns Unity3D-Entwickler eine Lücke hinterlassen.

Die Datei UnityAppController.h enthält neben der Deklaration von Variablen und Methoden auch eine Makrodefinition:

 #define IMPL_APP_CONTROLLER_SUBCLASS(ClassName) ... 

Dieses Makro ermöglicht lediglich das Überschreiben des Anwendungsdelegierten. Dazu müssen Sie einige einfache Schritte ausführen:

  1. Schreiben Sie Ihren eigenen Anwendungsdelegierten in Objective-C
  2. Fügen Sie irgendwo im Quellcode die folgende Zeile hinzu
     IMPL_APP_CONTROLLER_SUBCLASS(___) 
  3. Legen Sie diese Quelle im Ordner Plugins / iOS Ihres Unity3D-Projekts ab

Jetzt erhalten Sie ein Projekt, in dem der Standard-Unity3D-Anwendungsdelegierte durch Ihren benutzerdefinierten ersetzt wird.

Wie funktioniert das Makro zum Ersetzen von Delegaten?


Schauen wir uns den vollständigen Quellcode des Makros an:

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

Wenn Sie dieses Makro in Ihrer Quelle verwenden, wird der im Makro beschriebene Code in der Kompilierungsphase zum Hauptteil Ihrer Quelle hinzugefügt. Dieses Makro führt Folgendes aus. Zunächst wird die Lademethode zur Schnittstelle Ihrer Klasse hinzugefügt. Eine Schnittstelle im Kontext von Objective-C kann als Sammlung öffentlicher Felder und Methoden betrachtet werden. In C # wird in Ihrer Klasse eine statische Lademethode angezeigt, die nichts zurückgibt. Als nächstes wird die Implementierung dieser Lademethode zum Code Ihrer Klasse hinzugefügt. Bei dieser Methode wird die Variable AppControllerClassName deklariert, bei der es sich um ein Array vom Typ char handelt, und dieser Variablen wird dann ein Wert zugewiesen. Dieser Wert ist der Zeichenfolgenname Ihrer Klasse. Offensichtlich reichen diese Informationen nicht aus, um den Funktionsmechanismus dieses Makros zu verstehen. Daher sollten wir verstehen, was diese Lademethode ist und warum eine Variable deklariert wird.

Die offizielle Dokumentation besagt, dass load eine spezielle Methode ist, die für jede Klasse (insbesondere die Klasse, nicht ihre Instanzen) zu einem sehr frühen Zeitpunkt des Anwendungsstarts einmal aufgerufen wird, noch bevor die Hauptfunktion aufgerufen wird. Die Objective-c-Laufzeitumgebung (Laufzeitumgebung) beim Start der Anwendung registriert alle Klassen, die während des Anwendungsbetriebs verwendet werden, und ruft die Lademethode auf, falls implementiert. Es stellt sich heraus, dass bereits vor dem Start eines Codes in unserer Anwendung die Variable AppControllerClassName zu Ihrer Klasse hinzugefügt wird.

Dann könnten Sie denken: "Und was bringt es, diese Variable zu haben, wenn sie innerhalb der Methode deklariert ist und beim Beenden dieser Methode aus dem Speicher gelöscht wird?" Die Antwort auf diese Frage liegt etwas außerhalb der Grenzen von Ziel-C.

Und wo ist C ++?


Schauen wir uns die Deklaration dieser Variablen noch einmal an

 extern const char* AppControllerClassName; 

Das einzige, was in dieser Deklaration möglicherweise unverständlich ist, ist der externe Modifikator. Wenn Sie versuchen, diesen Modifikator in reinem Objective-C zu verwenden, gibt Xcode einen Fehler aus. Tatsache ist, dass dieser Modifikator nicht Teil von Objective-C ist, sondern in C ++ implementiert ist. Objective-C lässt sich kurz und bündig beschreiben, indem man sagt, es sei "C-Sprache mit Klassen". Es ist eine Erweiterung der C-Sprache und ermöglicht die unbegrenzte Verwendung von C-Code, der mit Objective-C-Code durchsetzt ist.

Um jedoch externe und andere C ++ - Funktionen verwenden zu können, müssen Sie einen Trick ausführen - verwenden Sie Objective-C ++. Es gibt praktisch keine Informationen zu dieser Sprache, da nur Objective-C-Code das Einfügen von C ++ - Code ermöglicht. Damit der Compiler berücksichtigt, dass einige Quelldateien als Objective-C ++ und nicht als Objective-C kompiliert werden sollten, müssen Sie nur die Erweiterung dieser Datei von .m in .mm ändern .

Der externe Modifikator selbst wird verwendet, um eine globale Variable zu deklarieren. Genauer gesagt, um dem Compiler zu sagen: „Glauben Sie mir, eine solche Variable existiert, aber der Speicher dafür wurde nicht hier, sondern in einer anderen Quelle zugewiesen. Und sie hat auch einen Wert, das garantiere ich. “ Daher erstellt unsere Codezeile einfach eine globale Variable und speichert den Namen unserer benutzerdefinierten Klasse darin. Es bleibt nur zu verstehen, wo diese Variable verwendet werden kann.

Zurück zum Haupt


Wir erinnern uns an das, was zuvor gesagt wurde - der Anwendungsdelegierte wird durch Angabe des Klassennamens erstellt. Wenn der Delegat mit dem konstanten Wert [myClass-Klasse] in der regulären Xcode-Projektvorlage erstellt wurde, haben die Mitarbeiter von Unity anscheinend entschieden, dass dieser Wert in eine Variable eingeschlossen werden soll. Mit der wissenschaftlichen Poke-Methode nehmen wir das von Unity3D generierte Xcode-Projekt und gehen zur Datei main.mm.

Darin sehen wir komplexeren Code als zuvor, ein Teil dieses Codes fehlt als unnötig:

 // 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; } 

Hier sehen wir die Deklaration dieser Variablen und die Erstellung des Anwendungsdelegierten mit seiner Hilfe.
Wenn wir einen benutzerdefinierten Delegaten erstellt haben, ist die erforderliche Variable vorhanden und bereits wichtig - der Name unserer Klasse. Durch Deklarieren und Initialisieren der Variablen vor der Hauptfunktion wird sichergestellt, dass sie einen Standardwert hat - UnityAppController.

Mit dieser Entscheidung sollte nun alles sehr klar sein.

Makroproblem


In den allermeisten Situationen ist die Verwendung dieses Makros natürlich eine großartige Lösung. Es ist jedoch erwähnenswert, dass dies eine große Gefahr darstellt: Sie können nicht mehr als einen benutzerdefinierten Delegaten haben. Dies liegt daran, dass, wenn zwei oder mehr Klassen das Makro IMPL_APP_CONTROLLER_SUBCLASS (Klassenname) verwenden, für den ersten der Wert der von uns benötigten Variablen zugewiesen wird und weitere Zuweisungen ignoriert werden. Und diese Variable ist eine Zeichenfolge, dh sie kann nicht mehr als einem Wert zugewiesen werden.

Sie könnten denken, dass dieses Problem entartet und in der Praxis unwahrscheinlich ist. Aber dieser Artikel wäre nicht passiert, wenn ein solches Problem nicht wirklich aufgetreten wäre, und selbst unter sehr seltsamen Umständen. Die Situation kann wie folgt sein. Sie haben ein Projekt, in dem Sie viele Analyse- und Werbedienstleistungen nutzen. Viele dieser Dienste verfügen über Objective-C-Komponenten. Sie sind schon lange in Ihrem Projekt und Sie kennen die Probleme mit ihnen nicht. Hier müssen Sie einen benutzerdefinierten Delegaten schreiben. Sie verwenden ein magisches Makro, um Probleme zu vermeiden, ein Projekt zu erstellen und einen Bericht über den Erfolg der Montage zu erhalten. Führen Sie das Projekt auf dem Gerät aus, und Ihre Funktionalität funktioniert nicht und Sie erhalten keinen einzigen Fehler.

Und die Sache kann sein, dass eines der Werbe- oder Analyse-Plugins dasselbe Makro verwendet. Im Plugin von AppsFlyer wird dieses Makro beispielsweise verwendet.

Was ist der Wert der externen Variablen bei mehreren Deklarationen?


Es ist interessant herauszufinden, ob dieselbe externe Variable in mehreren Dateien deklariert und in der Art unseres Makros (in der Lademethode) initialisiert wird. Wie können wir dann verstehen, welchen Wert die Variable annehmen wird? Um das Muster zu verstehen, wurde eine einfache Testanwendung erstellt, deren Code hier zu finden ist .

Das Wesentliche der Anwendung ist einfach. Es gibt 2 Klassen A und B, in beiden Klassen wird die externe Variable AexternVar deklariert, ihr wird ein bestimmter Wert zugewiesen. Die Werte der Variablen in den Klassen werden unterschiedlich festgelegt. In der Hauptfunktion wird der Wert dieser Variablen protokolliert. Experimentell wurde festgestellt, dass der Wert der Variablen von der Reihenfolge abhängt, in der die Quellen zum Projekt hinzugefügt werden. Die Reihenfolge, in der die Objective-C-Laufzeit Klassen während der Anwendungsausführung registriert, hängt davon ab. Wenn Sie das Experiment wiederholen möchten, öffnen Sie das Projekt und wählen Sie in den Projekteinstellungen die Registerkarte Phasen erstellen. Da das Projekt Test und klein ist, hat es nur 8 Quellcodes. Alle von ihnen sind auf der Registerkarte Build-Phasen in der Liste Compile Sources vorhanden.



Wenn in dieser Liste die Quelle der Klasse A höher ist als die Quelle der Klasse B, nimmt die Variable einen Wert aus der Klasse B an. Andernfalls nimmt die Variable einen Wert aus der Klasse A an.

Stellen Sie sich vor, wie viele Probleme dies theoretisch verursachen kann, ist eine kleine Nuance. Insbesondere wenn das Projekt riesig ist, automatisch generiert wird und Sie nicht wissen, in welchen Klassen eine solche Variable deklariert ist.

Lösung


Zu Beginn des Artikels wurde gesagt, dass Objective-C der C # -Reflexion einen Vorsprung verschaffen wird. Um unser Problem zu lösen, können Sie den Mechanismus Method Swizzling verwenden . Das Wesentliche dieses Mechanismus ist, dass wir die Möglichkeit haben, die Implementierung einer Methode einer Klasse während der Anwendung durch eine andere zu ersetzen. Daher können wir die interessierende Methode in UnityAppController durch eine benutzerdefinierte ersetzen. Wir übernehmen die vorhandene Implementierung und ergänzen den Code, den wir benötigen. Wir schreiben Code, der die vorhandene Implementierung der Methode durch die von uns benötigte ersetzt. Während der Arbeit der Anwendung arbeitet der Delegat, der das Makro verwendet, wie zuvor und ruft die Basisimplementierung von UnityAppController auf. Dort kommt unsere benutzerdefinierte Methode ins Spiel und wir erzielen das gewünschte Ergebnis. Dieser Ansatz ist in diesem Artikel gut geschrieben und veranschaulicht. Mit dieser Technik können wir eine Hilfsklasse erstellen - ein Analogon eines benutzerdefinierten Delegaten.In dieser Klasse schreiben wir den gesamten benutzerdefinierten Code, wodurch die benutzerdefinierte Klasse zu einer Art Wrapper wird, mit dem die Funktionalität anderer Klassen aufgerufen werden kann. Dieser Ansatz wird funktionieren, ist jedoch äußerst implizit, da es schwierig ist zu verfolgen, wo die Methode ersetzt wird und zu welchen Konsequenzen sie führen wird.

Eine andere Lösung für das Problem


Der Hauptaspekt des aufgetretenen Problems besteht darin, dass es viele benutzerdefinierte Delegaten gibt, oder Sie können nur einen haben oder ihn teilweise durch einen zweiten ersetzen. Gleichzeitig kann nicht sichergestellt werden, dass sich der Code von benutzerdefinierten Delegaten nicht in verschiedene Quelldateien einschleicht. Es stellt sich heraus, dass die Situation als Referenz betrachtet werden kann, wenn nur ein Delegat in der Anwendung vorhanden ist. Sie müssen in der Lage sein, beliebig viele benutzerdefinierte Klassen zu erstellen, während keine dieser Klassen das Makro verwendet, um Probleme zu vermeiden.

Die Sache ist klein, es bleibt zu bestimmen, wie dies mit Unity3D gemacht werden kann, während die Möglichkeit bleibt, ein Projekt mit einer Build-Maschine zu erstellen. Der Lösungsalgorithmus lautet wie folgt:

  1. Wir schreiben benutzerdefinierte Delegaten in der erforderlichen Menge, teilen die Logik der Plugins in verschiedene Klassen ein, beachten die Prinzipien von SOLID und greifen nicht auf Raffinesse zurück.
  2. UnityAppController XCode . UnityAppController .
  3. UnityAppController Unity .
  4. XCode UnityAppController ,

Der schwierigste Punkt aus dieser Liste ist zweifellos der letzte. Diese Funktion kann jedoch in Unity3D mithilfe des Post-Process-Build-Skripts implementiert werden. Ein solches Skript wurde eines schönen Abends geschrieben, Sie können es auf GitHub sehen .

Dieser Nachbearbeitungsprozess ist recht einfach zu verwenden. Wählen Sie ihn in einem Unity-Projekt aus. Schauen Sie im Inspektorfenster nach einem Feld mit dem Namen NewDelegateFile. Ziehen Sie Ihren geänderten UnityAppController in dieses Feld und speichern Sie ihn.



Beim Erstellen eines iOS-Projekts wird der Standarddelegierte durch einen geänderten ersetzt, und es ist kein manueller Eingriff erforderlich. Wenn Sie dem Projekt neue benutzerdefinierte Delegaten hinzufügen, müssen Sie nur noch die UnityAppController-Option ändern, die in Ihrem Unity-Projekt herumliegt.

PS


Vielen Dank an alle, die am Ende angekommen sind. Der Artikel war wirklich sehr lang. Ich hoffe die gemalten Informationen sind hilfreich.

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


All Articles