UniOSIntegrationKit

UniOSIntegrationKit 是在做之前的一个外包的时候面临需求临时写的一些代码和脚本的集合。之前给 Unity 写 iOS 插件,基本上都是 Unity 为主包,iOS 代码放在 Plugins 文件夹内。但是如果有一个现成的 iOS 项目需要插入 Unity 部分,则问题就比较多了,主要有这几点:

  • Unity Xcode 工程是代码生成,每次都会覆盖
  • Unity Xcode 工程自身有一个 AppDelegate
  • 直接把 Unity Xcode 文件拖入 iOS Xcode 工程会导致结构特别乱

UniOSIntegrationKit 主要解决的是第一和第二个问题,即在不改动 Unity Xcode 生成工程的前提下,使用各类方法(Method-Swizzling,Build Script)在外部对 Unity 的行为进行改动,避免每次重新生成 Unity Xcode 工程的时候需要重新修改。
有关 iOS 和 Unity 项目如何接入可以去看 Integrating Unity3D with native iOS application for Xcode 7 & Unity 5,这里的很多思路都是从程序的方式上去把这个视频做的事情自动化地做一遍。

NSObject+UnitySwizzling 是用来做 Method-Swizzling 的,不过也包括了对于 UnityWindow 的操作。Unity 的 AppDelegate 称之为 UnityAppController,如果进入看可以发现 .mm 文件里有 didFinishLaunchingWithOptions 等 delegate 方法的实现。而 iOS 本身自己已经有了一个 AppDelegate 文件,则在保留 iOS 的文件同时要把 Delegate 事件(软件进入后台,软件即将结束等)传给 Unity,所以使用 Method-Swizzling,关于此可以看 nshipster.cn,这里就直接给代码了,作用是在 iOS 本身的 AppDelegate 实现里都加上发给 UnityAppController 的代码。

首先,把 iOS 工程自带的 main.mm 替换为 unity 自带的 main.mm,然后修改

const char* AppControllerClassName = "AppDelegate"; // iOS 工程自己的实现了 AppDelegate 的那个类的名字

然后可以把 unity 的 main.mm 删除了,当然也可以直接删除 iOS 的 main.mm 修改 Unity 的,自己决定。

NSObject+UnitySwizzling.h

#import <Foundation/Foundation.h>
#import "AppDelegate.h"


@interface AppDelegate ()
@property(strong, nonatomic) UnityAppController *unityAppController;
@end

@interface AppDelegate (UnitySwizzling)

+ (NSMutableArray *)getUnityOriginalList;
+ (NSMutableArray *)getUnitySwizzlingList;
+ (NSMutableDictionary *)getUnitySwizzlingDict;

- (UIWindow *)getUnityWindow;
- (void)showUnityWindow;
- (void)hideUnityWindow;
@end

NSObject+UnitySwizzling.m

//
//  NSObject+UnitySwizzling.m
//
//  Created by Fincher Justin on 25/6/17.
//  Copyright © 2017年. All rights reserved.
//
#import <objc/runtime.h>
#import "NSObject+UnitySwizzling.h"


#import "AppDelegate.h"

#import "UnityAppController.h"
#import "UnityMessageCenter.h"


@implementation AppDelegate (UnitySwizzling)

+ (NSMutableArray *)getUnityOriginalList
{
    return [NSMutableArray arrayWithObjects:
            @"application:didFinishLaunchingWithOptions:",
            @"applicationWillResignActive:",
            @"applicationDidEnterBackground:",
            @"applicationWillEnterForeground:",
            @"applicationDidBecomeActive:",
            @"applicationWillTerminate:",
            nil];
}
+ (NSMutableArray *)getUnitySwizzlingList
{
    NSMutableArray *array = [[AppDelegate getUnityOriginalList] mutableCopy];
    for (int i = 0; i < [array count]; i ++)
    {
        NSString *originalString = (NSString *)[array objectAtIndex:i];
        NSString *swizzledString = [NSString stringWithFormat:@"xxx%@", originalString];
        [array replaceObjectAtIndex:i withObject:swizzledString];
    }
    return array;
}
+ (NSMutableDictionary *)getUnitySwizzlingDict
{
    return [NSMutableDictionary dictionaryWithObjects:
            [AppDelegate getUnitySwizzlingList]
                                              forKeys:
            [AppDelegate getUnityOriginalList]];
}

- (UIWindow *)getUnityWindow
{
    return UnityGetMainWindow();
}


- (void)showUnityWindow
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [[UnityMessageCenter sharedManager] runCommand:@"OnUniShow" WithParameter:@""];
        [[self getUnityWindow] makeKeyAndVisible];
    }];
    
}

- (void)hideUnityWindow
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [[self window] makeKeyAndVisible];
    }];
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        NSMutableDictionary *dict = [AppDelegate getUnitySwizzlingDict];
        NSArray* keys = [dict allKeys];
        
        for (NSString *originalSelectorString in keys)
        {
            NSString *swizzledSelectorString = dict[originalSelectorString];
            NSLog(@"Method Swizzling From %@ to %@ ",originalSelectorString,swizzledSelectorString);
            
            SEL originalSelector = NSSelectorFromString(originalSelectorString);
            SEL swizzledSelector = NSSelectorFromString(swizzledSelectorString);
            
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
            
            BOOL didAddMethod =
            class_addMethod(class,
                            originalSelector,
                            method_getImplementation(swizzledMethod),
                            method_getTypeEncoding(swizzledMethod));
            
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
        
    });
}

#pragma mark - Method Swizzling

- (BOOL)xxxapplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"xxxapplication:didFinishLaunchingWithOptions:launchOptions: Begin");
    [self xxxapplication:application didFinishLaunchingWithOptions:launchOptions];
    [self hideUnityWindow];
    
    self.unityAppController = [[UnityAppController alloc] init];
    [self.unityAppController application:application didFinishLaunchingWithOptions:launchOptions];
    
    NSLog(@"xxxapplication:didFinishLaunchingWithOptions:launchOptions: End");
    [self hideUnityWindow];
    return YES;
}

- (void)xxxapplicationWillResignActive:(UIApplication *)application
{
    [self.unityAppController applicationWillResignActive:application];
    [self xxxapplicationWillResignActive:application];
}


- (void)xxxapplicationDidEnterBackground:(UIApplication *)application
{
    NSLog(@"xxxapplicationDidEnterBackground: Begin");
    [self.unityAppController applicationDidEnterBackground:application];
    [self xxxapplicationDidEnterBackground:application];
}


- (void)xxxapplicationWillEnterForeground:(UIApplication *)application
{
    NSLog(@"xxxapplicationWillEnterForeground: Begin");
    [self.unityAppController applicationWillEnterForeground:application];
    [self xxxapplicationWillEnterForeground:application];
}


- (void)xxxapplicationDidBecomeActive:(UIApplication *)application
{
    NSLog(@"xxxapplicationDidBecomeActive: Begin");
    [self.unityAppController applicationDidBecomeActive:application];
    [self xxxapplicationDidBecomeActive:application];
}


- (void)xxxapplicationWillTerminate:(UIApplication *)application
{
    NSLog(@"xxxapplicationWillTerminate: Begin");
    [self.unityAppController applicationWillTerminate:application];
    [self xxxapplicationWillTerminate:application];
}
@end

UniOSIntegrationKitScript 是一个 Build Script,作用是将 UnityAppController.h 里的这段:

inline UnityAppController*  GetAppController()
{
    return (UnityAppController*)[UIApplication sharedApplication].delegate;
}

改为

inline UnityAppController*  GetAppController()
{
    return (UnityAppController*)[UIApplication sharedApplication].unityAppController;
}

原因是当我们使用 iOS 工程本身的实现了 AppDelegate 的文件后,对于 Unity Xcode 工程来说 [UIApplication sharedApplication].delegate 就不再是 UnityAppController 了,而 [UIApplication sharedApplication].unityAppController 才是,这是因为我们在之前的文件里声明了:

@interface AppDelegate ()
@property(strong, nonatomic) UnityAppController *unityAppController;
@end

UniOSIntegrationKitScript.sh

#!/bin/sh

#  UniOSIntegrationKitScript.sh
#
#  Created by Fincher Justin on 27/6/17.
#  Copyright © 2017年. All rights reserved.

set -x

echo "UniOSIntegrationKit"
find $SRCROOT/../ -name "UnityAppController.h"
find $SRCROOT/../ -name "UnityAppController.h" -print0 | xargs -0 sed -i "" "s/return (UnityAppController\*)\[UIApplication sharedApplication\]\.delegate;/return ((AppDelegate *)[UIApplication sharedApplication]\.delegate)\.unityAppController;/"

基本上问题一和二都差不多解决了,至于问题三我原本想把整个 Unity 部分打成一个 Pod,不过外包一直在拖,我也开学了,就没再继续下去。等什么时候还有这类集成 Unity 到 iOS 的项目再说吧。