背景知識
Unity 在導(dǎo)出 iOS 工程的時候支持兩種模式:mono 和 IL2CPP。
mono 就是傳統(tǒng)的虛擬機(jī)模式。這種模式只支持 32 位的 cpu帝璧,根據(jù)官方的答復(fù),以后也不會有更多的支持了。現(xiàn)在 iOS 上架 AppStore 必須要支持 arm64称龙,所以 mono 模式就是看看就好了。
IL2CPP IL(中間語言)轉(zhuǎn) C++戳晌,也就是把 C# 編譯成的 IL鲫尊,再從 IL 轉(zhuǎn)成 C++,然后跟 iOS 代碼一起混合編譯沦偎。所以基于 IL2CPP 的Unity 與 iOS 原生接口的相互調(diào)用疫向,本質(zhì)上就是 C++ 和 Objective C 的相互調(diào)用,理解了這個豪嚎,我們做互相調(diào)用的開發(fā)就很容易了搔驼。
Swift 參數(shù)與 C# 轉(zhuǎn)換
函數(shù)參數(shù)
例如 void UnitySendMessage(const char* obj, const char* method, const char* msg);
String 轉(zhuǎn) const char* => url.cString(using: String.defaultCStringEncoding)
函數(shù)返回值
我們不能在函數(shù)內(nèi)存申請局部變量去返回給 C# 用,因為函數(shù)退出的時候侈询,內(nèi)存就釋放了舌涨,那么我們需要用 malloc 去分配內(nèi)存。malloc 分配的內(nèi)存不需要調(diào)用 free扔字,也不用擔(dān)心會內(nèi)存泄露囊嘉,因為合成的代碼會自動幫忙處理的。
int 數(shù)組
static int *intArr = NULL;
int* getAllBotId() {
NSArray *array = [SwiftCommon getAllBotId];
intArr = malloc(array.count);
for (int i = 0; i < array.count; i++) {
intArr[i] = [[array objectAtIndex:i] intValue];
}
return intArr;
}
char 數(shù)組
char* getRobotCardsInfo() {
NSString *string = [SwiftCommon getRobotCardsInfo];
char* cStringCopy(const char* string);
return cStringCopy([string UTF8String]);
}
char* cStringCopy(const char* string){
if (string == NULL){
return NULL;
}
char* res = (char*)malloc(strlen(string)+1);
strcpy(res, string);
return res;
}
通信一:Unity 向 iOS 發(fā)消息
Unity 聲明一個 [DllImport( "__Internal" )] 函數(shù)
iOS .m 文件寫函數(shù)實現(xiàn)
通信二:iOS 向 Unity 發(fā)消息
方法一:UnitySendMessage
Unity 自身提供了一個函數(shù)革为,可以將信息通過字符串或 json 字符串傳入
/**
* iOS 主動發(fā)消息給 Unity
* @param obj Unity GameObject名稱
* @param method GameObject綁定腳本的方法
* @param msg 要傳遞的消息
*/
void UnitySendMessage(const char* obj, const char* method, const char* msg);
方法二: 指針
原理:C++ 里面有函數(shù)指針的概念扭粱,我們可以在 iOS 端實現(xiàn)一個函數(shù),接收函數(shù)指針篷角,Unity 側(cè)調(diào)用焊刹,把 Unity 的函數(shù)當(dāng)做指針傳過去,在 iOS 端把它存下來,后續(xù)使用虐块。
示例:將一個 string 傳到 unity, 代碼量比 UnitySendMessage 多俩滥,但這是為下一個例子做鋪墊,通過指針回調(diào)以一種更優(yōu)雅的方式傳回數(shù)據(jù)
TtsPlayeriOSProxy.cs
[MonoPInvokeCallback]
public static int playTtsUrl(string url) {
// 收到 iOS 傳來的 url
}
[DllImport("__Internal")]
private static extern void setTtsFunction(FunctionPlayer urlPlayer);
// ios call c# delegate
public delegate int FunctionPlayer(string url);
...
// 單例構(gòu)造函數(shù)
private TtsPlayeriOSProxy ()
{
setTtsFunction (playTtsUrl);
}
UnityPlugin.m
// C#設(shè)置過來的函數(shù)指針類型
typedef int (*PlayTtsUrlFunction)(const char *url);
static PlayTtsUrlFunction playTtsUrlFunc;
void setTtsFucntion(PlayTtsUrlFunction urlFunction) {
playTtsUrlFunc = urlFunction;
}
void playTtsUrl(const char* url) {
if (playTtsUrlFunc != nil) {
playTtsUrlFunc(url);
}
}
在 .swift 文件中可直接調(diào)用 playTtsUrl 函數(shù)將 url 傳遞到 Unity
知識補(bǔ)充:
1. C# delegate
C# 中的委托(Delegate)類似于 C 或 C++ 中函數(shù)的指針贺奠。委托(Delegate) 是存有對某個方法的引用的一種引用類型變量霜旧。
例如,假設(shè)有一個委托:
public delegate int MyDelegate (string s);
上面的委托可被用于引用任何一個帶有一個單一的 string 參數(shù)的方法儡率,并返回一個 int 類型變量挂据。
2. [DllImport( "__Internal" )]
private static extern void sendRequest(int requestId, byte[] data, int dataLength);
[DllImport( "__Internal" )] 表示這個函數(shù)位于Dll中,DLL名字是 __Internal儿普,這是固定語法崎逃,意思是這個函數(shù)是靜態(tài)鏈接在 iOS 的 App 中的。extern 表示是一個外部的函數(shù)眉孩。
3. [MonoPInvokeCallback]
用來標(biāo)記這個函數(shù)會被iOS側(cè)反向調(diào)用
通信三:Unity 將任務(wù)交付 iOS 處理个绍,結(jié)果回調(diào) Unity
示例:Unity 組裝數(shù)據(jù) encode 成 byte 數(shù)組交付 iOS 發(fā)起網(wǎng)絡(luò)請求
Unity
NetworkManageriOSProxy.cs 單例類
[DllImport( "__Internal" )]
private static extern void sendRequest(int requestId, byte[] data, int dataLength);
[DllImport("__Internal")]
private static extern int setFunctionPointerOnResponse(FunctionPointerOnResponse pointer);
// ios call c# delegate
public delegate void FunctionPointerOnResponse(int reqeustId, int errorCode, IntPtr responseData, int responseDataLength);
[MonoPInvokeCallback]
// 需要 length 重組數(shù)據(jù)
static public void onResponse(int reqeustId, int errorCode, IntPtr responseData, int responseDataLength)
{
if (errorCode == 0)
{
byte[] buffer = new byte[responseDataLength];
Marshal.Copy(responseData, buffer, 0, responseDataLength);
NetworkManagerProxy.onResponse(reqeustId, errorCode, buffer);
}
}
...
private static bool isCallbackSeted = false;
public static void sendRequestForiOS(int requestId, byte[] data)
{
if (!isCallbackSeted) {
setFunctionPointerOnResponse(onRespone);
isCallbackSeted = true;
}
sendRequest(requestId, data, data.Length);
}
針對 onResponse 函數(shù)的 responseData 參數(shù)做一個解釋
iOS c++: void onRespone(int reqeustId, int errorCode, const void responseData*, int responseDataLength);
Unity c#: static public void onResponse(int reqeustId, int errorCode, IntPtr responseData, int responseDataLength);
iOS側(cè) 調(diào)用 C# 的時候,基本類型是可以直接映射的浪汪,要注意的是巴柿,如果是數(shù)組之類的參數(shù),在 iOS 側(cè)用 C++ 的表現(xiàn)是指針死遭,這個其實是內(nèi)存里面的 rawdata广恢,會以一個 IntPtr 的參數(shù)傳遞到 Unity 側(cè),我們知道在 C# 中數(shù)組是個對象呀潭,我們要把它轉(zhuǎn)為“托管的對象”钉迷。上面的代碼有示例如何轉(zhuǎn)換。
iOS
.m文件
// C#設(shè)置過來的函數(shù)指針類型
typedef void (*FunctionPointerOnResponse)(int reqeustId, int errorCode, void* responseData, int responseDataLength);
// 用于保存回調(diào)指針的
static FunctionPointerOnResponse callbackFunction;
void setFunctionPointerOnResponse(FunctionPointerOnResponse pointer) {
callbackFunction = pointer;
}
// 需要 length 重組數(shù)據(jù)
void sendRequest(int requestId, Byte* data, int dataLength) {
NSData *body = [[NSData alloc] initWithBytes:data length:dataLength];
[SwiftCommon sendUnityRequest:requestId body:body];
}
// 收到數(shù)據(jù)的時候回調(diào)此接口
void onRespone(int reqeustId, int errorCode, const void* responseData, int responseDataLength) {
if (callbackFunction) {
callbackFunction(reqeustId, errorCode, (void *)(responseData), responseDataLength);
}
}
SwiftCommon.swift
/**
OC調(diào)用swift中不支持的類型的方法
*/
class SwiftCommon: NSObject {
// 偽代碼
@objc static func sendUnityRequest(_ requestId: Int, body: NSData) {
let request = xxx
NetworkEngine.shared.sendRequest(request, success: { response in
guard var data = response.data else {
return
}
let dataLength = Int32(data.count)
data.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<UInt8>) -> Void in
// 回傳給 unity
onRespone(Int32(requestId), response.ret, bytes, dataLength)
})
}) { error in
}
}
}
通信四:iOS 將任務(wù)交給 Unity 處理蜗侈,結(jié)果回調(diào) iOS
Unity
通信三中寫了如何將指針傳入iOS篷牌, 這里不再贅述傳遞 start 和 stop 指針的 c# 代碼
iOSASRListener.cs
[DllImport("__Internal")]
private static extern void _HandleASRSuccessed(string requestId, bool isFinish, string msgText, string rspMsg, byte[] msgResponse, int msgResponeLength);
[DllImport("__Internal")]
private static extern void _HandleASRFailed(string requestId, int errorCode);
iOS
AsrProxy.m
#ifdef __cplusplus
extern "C" {
#endif
// C#設(shè)置過來的函數(shù)指針類型
typedef void (*FunctionPointerForAsr)(void);
static FunctionPointerForAsr start;
static FunctionPointerForAsr stop;
void setFunctionPointerForAsr(FunctionPointerForAsr startFunc, FunctionPointerForAsr stopFunc)
{
start = startFunc;
stop = stopFunc;
}
void StartAsr()
{
if (start)
{
start();
}
}
void StopAsr()
{
if (stop)
{
stop();
}
}
#ifdef __cplusplus
}
#endif
AsrListenerProxy.m
extern void (^ __nonnull HandleASRSuccessed_SwiftCallBack)(const char* __nonnull requestId, bool isFinish, const char* __nonnull msgText, const char* __nonnull rspMsg, NSData* msgResponseData) = NULL;
extern void (^ __nonnull HandleASRFailed_SwiftCallBack)(const char* __nonnull requestId, int errorCode) = NULL;
void _HandleASRSuccessed(const char* requestId, bool isFinish, const char* msgText, const char* rspMsg, Byte* msgResponse, int msgResponeLength)
{
if (HandleASRSuccessed_SwiftCallBack != NULL ){
NSData * data = [[NSData alloc]initWithBytes:msgResponse length:msgResponeLength];
HandleASRSuccessed_SwiftCallBack(requestId,isFinish,msgText,rspMsg,data);
}
}
void _HandleASRFailed(const char* requestId, int errorCode)
{
if (HandleASRFailed_SwiftCallBack != NULL ){
HandleASRFailed_SwiftCallBack(requestId,errorCode);
}
}
AsrService.swift
@objc protocol AsrServiceDelegate {
func asrSuccessed(requestId: String, isFinish: Bool, msgText: String, rspMsg: String, response: UniSendMsgResponse?)
func asrFailed(requestId: String, errorCode: Int32)
}
class AsrService {
weak var delegate: AsrServiceDelegate?
init() {
bridgingCFunction()
}
convenience init(delegate: AsrServiceDelegate) {
self.init()
self.delegate = delegate
}
fileprivate func bridgingCFunction() {
HandleASRSuccessed_SwiftCallBack = handleASRSuccessed
HandleASRFailed_SwiftCallBack = handleASRFailed
}
func startAsr() {
StartAsr()
}
func stopAsr() {
StopAsr()
}
fileprivate func handleASRSuccessed(requestId: UnsafePointer<Int8> , isFinish: Bool, msgText: UnsafePointer<Int8>, rspMsg: UnsafePointer<Int8>, msgResponseData: Data?) {
var response: UniSendMsgResponse?
if let `msgResponseData` = msgResponseData {
response = UniSendMsgResponse.fromData(msgResponseData) as? UniSendMsgResponse
}
self.delegate?.asrSuccessed(requestId: requestId.stingValue, isFinish: isFinish, msgText: msgText.stingValue, rspMsg: rspMsg.stingValue, response: response)
}
fileprivate func handleASRFailed(requestId: UnsafePointer<Int8> , errorCode: Int32) {
self.delegate?.asrFailed(requestId: requestId.stingValue, errorCode: errorCode)
}
}
踩坑
MonoPInvokeCallback屬性無法找到
這個是舊版本 Unity 才有的一個屬性類,其實就是用來標(biāo)記這個函數(shù)會被 iOS 側(cè)反向調(diào)用的踏幻,也沒有什么實質(zhì)性的意義枷颊。但是新版本去掉了導(dǎo)致編譯不過。所以就在工程里面添加一個就行了该面。
// 參考這個帖子的說明 https://garry.tv/2018/02/15/steamworks-and-il2cpp/
internal class MonoPInvokeCallbackAttribute : Attribute
{
public MonoPInvokeCallbackAttribute() { }
}