Unity3D: Modifying the delegate of an iOS application

 3r33590. 3r3-31. I think many in the course of developing a game for iOS had to deal with the fact that there is a need to use this or that native functionality. With regards to Unity3D, there can be a lot of problems in this issue: in order to implement a feature, you have to look towards native plug-ins written in Objective-C. Someone at this moment immediately despair and throws the idea. Someone is looking for ready-made solutions in AssetStore or on forums, hoping that a ready-made solution already exists. If there are no ready-made solutions, then the most resistant of us see no other way out than to dive into the depths of iOS programming and the interaction of Unity3D with Objective-C code. 3r? 3577.  3r33590. 3r? 3577.  3r33590. Those who choose the last path (although I think they themselves know) will face many problems on this difficult and thorny path: 3r37777.  3r33590. 3r? 3577.  3r33590.
 3r33590. 3r33550. iOS is a completely unfamiliar and detached ecosystem developing in its own way. At a minimum, you have to spend quite a lot of time to figure out how to get close to the application, and where in the depths of the automatically generated XCode of the project there is the Unity3D interaction code of the engine with the native component of the application. 3r? 3551.  3r33590. 3r33550. Objective-C is a rather detached and little similar programming language. And when it comes to interacting with the C ++ code of the Unity3D application, a “dialect” of this language, called Objective-C ++, comes on the scene. There is very little information about him, most of which are ancient and archival. 3r? 3551.  3r33590. 3r33550. The Unity3D interaction protocol with the iOS application is rather poorly described. You should only rely on tutorials of enthusiasts in the network who write how to develop the simplest native plugin. At the same time, very few people touch upon deeper questions and problems arising from the need to do something complicated. 3r? 3551.  3r33590.
3r? 3577.  3r33590. Those who want to learn about the mechanisms of Unity3D interaction with the iOS application, please under the cat. 3r? 3577.  3r33590. Home Screen Quick Actions Or just tachu Inkorku 3r33551.  3r33590. 3r33550. interaction with WatchKit or HealthKit 3r33551.  3r33590. 3r33550. opening and processing URLs from another application. If this URL refers to your application, you can process it in your application instead of letting the system open this URL in the browser 3r33551.  3r33590. 3r33553. 3r? 3577.  3r33590. This is not the whole list of scenarios. In addition, it is worth noting that the delegate modifies many analytics and advertising systems in their native plugins. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. How Unity3D implements the application delegate 3r33528. 3r? 3577.  3r33590. Let's now look at the XCode project generated by Unity3D and find out how the application delegate is implemented in Unity3D. When building for the iOS platform, Unity3D automatically generates an Xcode project for you, which uses quite a lot of generic code. This template code also includes the Delegate code for the Application. Inside any generated project you can find files. UnityAppController.h and 3r3r436. UnityAppController.mm [/i] . These files contain the code of the UnityAppController class of interest. 3r? 3577.  3r33590. 3r? 3577.  3r33590. In fact, Unity3D uses a modified version of the “Single View Application” template. Only in this template Unity3D uses the application delegate not only to handle iOS events, but also to initialize the engine itself, prepare graphic components and much more. It is very easy to understand if you look at the 3r3777 method.  3r33590. 3r? 3577.  3r33590.
- (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (NSDictionary *) launchOptions

3r? 3577.  3r33590. in the UnityAppController class code. This method is called at the time of application initialization, when you can transfer control to your custom code. Inside this method, for example, you can find the following lines: 3r37777.  3r33590. 3r? 3577.  3r33590.
UnityInitApplicationNoGraphics ([[[NSBundle mainBundle]BundlePath]UTF8String]); 3r33590. 3r33590.[self selectRenderingAPI]; 3r33590.[UnityRenderingView InitializeForAPI: self.renderingAPI]; 3r33590. 3r33590. _window =[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds]; 3r33590. _unityView =[self createUnityView]; 3r33590. 3r33590.[DisplayManager Initialize]; 3r33590. _mainDisplay =[DisplayManager Instance].mainDisplay; 3r33590.[_mainDisplay createWithWindow: _window andView: _unityView]; 3r33590. 3r33590.[self createUI]; 3r33590.[self preStartUnity]; 3r33590.
3r? 3577.  3r33590. Even without going into the details of what these calls do, one can guess that they are related to preparing Unity3D for work. It turns out the following scenario:
 3r33590. 3r? 3577.  3r33590. 3r? 3539.  3r33590. 3r33550. The main function from is called. main.mm 3r? 3551.  3r33590. 3r33550. An instance of the application classes and its delegate are created.  3r33590. 3r33550. The application delegate prepares and launches the Unity3D engine 3r33551.  3r33590. 3r33550. Your custom code starts working. If you use il2cpp, then your code is translated from C # to IL and then to C ++ code that directly goes into the XCode project. 3r? 3551.  3r33590. 3r33553. 3r? 3577.  3r33590. This scenario sounds quite simple and logical, but carries with it a potential problem: how can we modify the application delegate if we do not have access to the source code when working in Unity3D? 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. Unity3D backlog for modifying the application delegate 3r33528. 3r? 3577.  3r33590. We can look at the files AppDelegateListener.mm/.h . They contain macros that allow you to register any class as a listener for the application delegate events. This is a good approach, we do not need to modify the existing code, but just add new ones.th But there is a significant drawback: not all the events of the application are supported and there is no possibility to get information about the launch of the application. 3r? 3577.  3r33590. 3r? 3577.  3r33590. The most obvious, however, unacceptable way out is to change the delegate’s source code with your hands after Unity3D builds the XCode project. The problem with this approach is obvious - the option will work if you do the assembly with your hands and you are not confused by the need to modify the code manually after each assembly. In the case of using collectors (Unity Cloud Build or any other build machine) this option is absolutely unacceptable. For these purposes, the developers of Unity3D left us a loophole. 3r? 3577.  3r33590. 3r? 3577.  3r33590. In the file 3r3436. UnityAppController.h [/i] in addition to the declaration of variables and methods, the definition of the macro is also contained: 3r37777.  3r33590. 3r? 3577.  3r33590.
#define IMPL_APP_CONTROLLER_SUBCLASS (ClassName)

3r? 3577.  3r33590. This macro just gives the opportunity to override the application delegate. To do this, you need to take a few simple steps: 3r33577.  3r33590. 3r? 3577.  3r33590. 3r? 3539.  3r33590. 3r33550. Write your own application delegate on Objective-C
 3r33590. 3r33550. Somewhere inside the source code add the following line
IMPL_APP_CONTROLLER_SUBCLASS (class_name_of your_class)
3r? 3551.  3r33590. 3r33550. Put this source inside the folder of your Unity3D
Plugins /iOS folder.  3r33590. 3r33553. 3r? 3577.  3r33590. Now you will receive a project in which the standard Unity3D delegate of the application will be replaced with your custom one. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. How does the macro replace delegate 3r33528. 3r? 3577.  3r33590. Let's look at the full source code of the macro:
 3r33590. 3r? 3577.  3r33590.
#define IMPL_APP_CONTROLLER_SUBCLASS (ClassName)
@interface ClassName (OverrideAppDelegate)
{3r33590.} 3r33590. + (void) load; 3r33590. @end
@implementation ClassName (OverrideAppDelegate)
+ (void) load
{3r33590. extern const char * AppControllerClassName; 3r33590. AppControllerClassName = #ClassName; 3r33590.} 3r33590. @end

3r? 3577.  3r33590. Using this macro in your source code will add the code described in the macro to the body of your source code at the compilation stage. This macro does the following. First, it adds a load method to the interface of your class. An interface in the context of Objective-C can be viewed as a set of public fields and methods. Speaking in C #, a static load method will appear in your class that returns nothing. Next, the implementation of this load method will be added to the code of your class. In this method, the variable AppControllerClassName will be declared, which is an array of type char, and then this variable will be assigned a value. This value is the string name of your class. Obviously, this information is not enough to understand the mechanism of this macro, so we need to figure out what this “load” method is and why the variable is declared. 3r? 3577.  3r33590. 3r? 3577.  3r33590. In official documentation 3r33557. says that load is a special method that is called once for each class (specifically a class, not its instances) at the earliest stage of the application launch, before the main function is called. The Objective-c (runtime) runtime at the start of the application will register all classes that will be used during the operation of the application and call their load method, if it is implemented. It turns out that even before the start of any code of our application, the variable AppControllerClassName will be added to your class. 3r? 3577.  3r33590. 3r? 3577.  3r33590. Here you might think: “And what’s the point of having this variable, if it is declared inside a method and will be destroyed from memory, when exiting this method?”. The answer to this question lies a bit beyond Objective-C. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. And here With ++? 3r33528. 3r? 3577.  3r33590. Let's take another look at declaring this variable
 3r33590. 3r? 3577.  3r33590.
extern const char * AppControllerClassName;
3r? 3577.  3r33590. The only thing that can be incomprehensible in this declaration is the extern modifier. If you try to use this modifier in pure Objective-C, then Xcode will generate an error. The fact is that this modifier is not part of Objective-C, it is implemented in C ++. Objective-C can be described quite succinctly, saying that it is a “C language with classes”. It is an extension of the C language and allows unlimited use of C code in conjunction with Objective-C code. 3r? 3577.  3r33590. 3r? 3577.  3r33590. However, to use extern and other features of C ++ you need to go for some trick - use Objective-C ++. There is practically no information about this language, due to the fact that it is just Objective-C code that can be inserted into C ++ code. In order for the compiler to consider that some source file should be compiled as Objective-C ++, and not Objective-C you just need to change the extension of this file from 3r3436. .m 3r3437. on 3r3436. .mm [/i] . 3r? 3577.  3r33590. 3r? 3577.  3r33590. The extern modifier itself is used to declare a global variable. More precisely, to tell the compiler, “Believe me, such a variable exists, but the memory for it was allocated not here, but in another source. And she has value too, I guarantee. ” Thus, our line of code simply creates a global variable and stores in it the name of our custom class. It remains only to understand where this variable can be used. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. Back to main
3r? 3577.  3r33590. We recall what was said earlier - the application delegate is created by specifying the name of the class. If in the usual Xcode template of the project a delegate was created using the constant value[myClass class], then, apparently, the guys from Unity decided that this value should be wrapped in a variable. Using the scientific method, take the XCode project generated by Unity3D and go to the file main.mm . 3r? 3577.  3r33590. 3r? 3577.  3r33590. In it, we see a more complex code than before, part of this code was missed as irrelevant: 3r37777.  3r33590. 3r? 3577.  3r33590.
//WARNING: this MUST be c decl (NSString ctor will be called after 3). const char * AppControllerClassName = "UnityAppController"; 3r33590. 3r33590. int main (int argc, char * argv[])
{3r33590. 3r33590. UIApplicationMain (argc, argv, nil,[NSString stringWithUTF8String: AppControllerClassName]); 3r33590.} return 0; 3r33590.}

3r? 3577.  3r33590. Here we see the declaration of this variable itself, and the creation of the application delegate with its help. 3r? 3577.  3r33590. If we created a custom delegate, then the desired variable exists and already matters - the name of our class. And declaring and initializing a variable before the main function ensures that it has a default value, UnityAppController. 3r? 3577.  3r33590. 3r? 3577.  3r33590. Now with this decision should be all very clear. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. The problem of macro
3r? 3577.  3r33590. Of course, for the vast majority of situations, using this macro is a great solution. But it is worth noting that there is a large pitfall in it: you cannot have more than one custom delegate. This happens because if 2 or more classes use the IMPL_APP_CONTROLLER_SUBCLASS (ClassName) macro, then for the first one, the value of the variable we need will be assigned, and further assignments are ignored. And this variable is a string, that is, it cannot be assigned more than one value. 3r? 3577.  3r33590. 3r? 3577.  3r33590. You might think that this problem is degenerate and in practice is unlikely. But, this article would not have happened if such a problem had not really happened, and even under very strange circumstances. The situation may be as follows. You have a project in which you use a lot of analytics and advertising services. Many of these services have Objective-C components. They have long been in your project and you do not know the troubles with them. Here you need to write a custom delegate. You use a magic macro, designed to save you from problems, collect the project and get a report on the success of the assembly. Run the project on the device, and your functionality does not work and at the same time you do not get a single error. 3r? 3577.  3r33590. 3r? 3577.  3r33590. But the point may be that one of the plug-ins of advertising or analytics uses the same macro. For example, in the plugin from
AppsFlyer
This macro is used. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. What value will the extern variable take in case of multiple declarations? 3r33528. 3r? 3577.  3r33590. It is interesting to understand, if the same extern variable is declared in several files, and they are initialized in the manner of our macro (in the load method), how can you understand what value the variable will take? To understand the pattern, a simple test application was created, the code of which can be viewed here is 3r33557. . 3r? 3577.  3r33590. 3r? 3577.  3r33590. The essence of the application is simple. There are 2 classes A and B, in both classes the extern variable AexternVar is declared, it is assigned a specific value. Variable values ​​in classes are set differently. In the main function, the value of this variable is output to the log. Experimentally, it turned out that the value of a variable depends on the order in which source codes are added to the project. The order in which the Objective-C runtime environment will register classes during the course of an application depends on this. If you want to repeat the experiment, open the project and select the Build Phases tab in the project settings. Since the project is test and small - there are only 8 sources in it. All of them are present on the Build Phases tab in the Compile Sources list. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r3502. 3r? 3577.  3r33590. 3r? 3577.  3r33590. If in this list the source of class A is higher than the source of class B, then the variable will take a value from class B. Otherwise, the variable will take a value from class A. 3r37777.  3r33590. 3r? 3577.  3r33590. Just imagine how many problems this small nuance can theoretically cause. Especially if the project is huge, automatically generated and you do not know in which classes such a variable is declared. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. Solution of the problem 3r33528. 3r? 3577.  3r33590. Earlier in the article it was already said that Objective-C will give odds to C # Reflection. Specifically, to solve our problem, you can use a mechanism called 3r31919. Method Swizzling . The essence of this mechanism is that we have the opportunity to replace the implementation of a method of any class with another in the course of the application. Thus, we can replace the method we are interested in in UnityAppController with a custom one. We take existing implementation and we supplement with the code necessary to us. We write code that replaces the existing implementation of the method with the one we need. In the course of the application, the delegate using the macro will work as before, calling the base implementation from UnityAppController, and then our custom method will come into play and we will achieve the desired result. Such an approach is well-written and illustrated in 3r33521. This article 3r3557. . With the help of this technique, we can make an auxiliary class - an analogue of the custom delegate. In this class, we will write the entire custom code, making the custom class a kind of Wrapper to call the functionality of other classes. This approach will work, but it is extremely implicit due to the fact that it is difficult to track where the method is being replaced and what consequences it will have. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r33535. Another solution to the problem is 3r33528. 3r? 3577.  3r33590. The main aspect of the problem that has happened is that there are many who want to have custom delegates, but only one, or partially replaced by the second. At the same time, there is no possibility to make the code of custom delegates not crawl along different source files. It turns out that a reference situation can be considered when there is only one delegate in the application, you need to be able to create custom classes as many as you want, and none of these classes use the macro to avoid problems. 3r? 3577.  3r33590. 3r? 3577.  3r33590. Things are going well, it remains to determine how this can be done using Unity3D, while leaving the possibility of building the project using a build machine. The solution algorithm is as follows: 3r33577.  3r33590. 3r? 3577.  3r33590. 3r? 3539.  3r33590. 3r33550. We write custom delegates in the required quantity, dividing the logic of plug-ins into different classes, observing the principles of SOLID and not resorting to sophistication. 3r? 3551.  3r33590. 3r33550. To avoid using macros, take the UnityAppController source from the generated Xcode project and modify it for our own needs. Directly we create copies of the necessary classes and we cause methods of these classes from UnityAppController. 3r? 3551.  3r33590. 3r33550. We save our modified UnityAppController and add a project to our Unity. 3r? 3551.  3r33590. 3r33550. We are looking for an opportunity when building the XCode of the project is automated to replace the standard UnityAppController with the one that we saved in the project 3r33551.  3r33590. 3r33553. 3r? 3577.  3r33590. The most difficult item from this list is undoubtedly the last. However, this feature can be implemented in Unity3D by means of the post process build script. This script was written on one beautiful night, you can see it 3r33556. on github
. 3r? 3577.  3r33590. 3r? 3577.  3r33590. This post process is quite simple to use, you select it in the Unity project. Look in the Inspector window and see a field called NewDelegateFile there. Drag and drop into this field your modified version of UnityAppController'a and save. 3r? 3577.  3r33590. 3r? 3577.  3r33590. 3r3566. 3r? 3577.  3r33590. 3r? 3577.  3r33590. When building an iOS project, the standard delegate will be replaced with a modified one, with no manual intervention required. Now, when adding new custom delegates to the project, you will only need to modify the UnityAppController variant that you have in the Unity project. 3r? 3577.  3r33590. 3r? 3577.   3r33590. 3r33575. P.S. 3r33576. 3r? 3577.  3r33590. Thanks to everyone who got to the end, the article really turned out to be extremely long. Hopefully, the written information will be useful. 3r33586. 3r33590. 3r33590. 3r33590. 3r?383. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r33584. 3r33590. 3r33586. 3r33590. 3r33590. 3r33590. 3r33590.
+ 0 -

Add comment