
第一次接 Unity 的 iOS SDK我其实也头疼了好几天。当时项目要上小7手游渠道主程丢给我一句话“把 iOS 的 SDK 也接一下。”我第一反应是完了要开始看 Objective-C 了。我平时主要写 C#Xcode 都没碰过几次更别提真机签名、URL Scheme、回调协议这些听起来就很高级的术语。真正做下来之后发现事情确实没有想象中那么难。我说的“不难”不是不需要 Mac、不需要 Xcode而是指整个接入流程非常标准化拿对 SDK 包、配好工程、接上回调跑通一个登录和一个支付后面的路子就全顺了。这篇文章想把整个流程和踩过的坑完整过一遍适合 Unity 开发者、独立游戏团队以及那些被老板突然安排“把 iOS 也接了吧”的同路人。1. 先搞清楚“接入 iOS SDK”到底是个什么事1.1 小7手游SDK是干什么的渠道SDK说白了就是一个“商场收银台”。你游戏好比一个店铺玩家要进来玩、要付钱商场要求所有店铺必须用统一的会员卡和统一的收银系统。你接了小7手游的 SDK玩家才能用小7的账号体系一键登录才能通过小7的支付通道付款平台那边才能识别你的游戏数据、给你结算分成。从开发者角度来看它主要提供三块能力账号登录、支付下单、数据上报。登录不用多说支付也不是让你自己去接苹果支付而是由渠道给你封装好一套收银台。数据上报则是把激活、注册、付费这些事件告诉平台方便对账和买量。重点在于这个 SDK 在 iOS 这一端是原生的 framework 和头文件不是 Unity 能直接引用的 C# 库。Unity 工程本质上运行在一个 iOS 应用容器里你要做的就是让 Unity 的 C# 代码和这个原生 SDK 之间能对上话。搞清楚这一点后面所有步骤都会有方向感。1.2 为什么这么多人说难我总结下来大家觉得难主要是因为四个坎。第一个坎是环境门槛。Android 在 Windows 上就能搞iOS 必须要有一台 Mac还要装 Xcode。很多中小团队不是没有 Mac而是没有“专门用来搞 iOS 包”的 Mac借来借去时间都耗在环境上。第二个坎是语言。Unity 开发者平时写 C#看到 .h 和 .m 文件里一堆方括号语法第一反应就是头大。可实际上接入 SDK 你根本不需要会写复杂的 Objective-C 逻辑你只需要在几个固定位置抄几段代码再补一个桥接文件就行。第三个坎是回调机制。C# 里习惯了事件和委托而 iOS SDK 给 Unity 回消息的时候用的是 UnitySendMessage它是通过 GameObject 名字找到 Unity 里的对象再调用指定方法。这个概念对新手来说很绕一旦名字对不上回调就静默丢失完全不知道发生了什么。第四个坎是教程过时。Unity 从 2019.3 之后 iOS 工程结构改过一轮很多老教程还在让你去改 AppDelegate.mm你兴冲冲找过去发现文件根本不在原来的位置。版本错位会让接入过程变得特别痛苦。但好消息是这四个坎都不需要靠“重修 iOS 开发”来解决只要按照流程走它们都是标准化操作。1.3 真正要做的事接线我后来把整个接入过程抽象成一句话你是在“接线”不是在“写 iOS 程序”。SDK 在你眼里应该是一个黑盒。你不用管盒子里怎么实现登录态、怎么加密、怎么跟服务器通信你只需要处理两条线。一条是 C# 调原生你在 C# 里声明一个 extern 方法调用之后能穿透到 iOS 原生层让原生代码去操作 SDK。另一条是原生调 C#SDK 发生登录成功、支付成功等事件后原生代码把结果拼成字符串通过 UnitySendMessage 丢回给 Unity 里的某个 GameObject 方法。这两条线理清了整个接入就已经完成 80% 了。剩下的 20% 就是环境配置、签名、Info.plist 这些“脏活”。你不需要懂 iOS 内存管理也不需要懂 RunLoop甚至不需要会看复杂的崩溃日志只需要知道出了错往哪个方向查。2. 动手之前把这些材料准备好能少踩一半坑2.1 Mac 环境与 Xcode先解决硬件问题。编译 iOS 包必须有 Mac装好 Xcode 是前提。如果你手里是公司的 Mac建议先更新到最新稳定版 Xcode但注意别为了尝鲜升级 beta 版。SDK 的 framework 编译版本如果比你的 Xcode 新链接可能报错你的 Xcode 太新而 SDK 没跟上也可能遇到奇怪的警告不过一般能编过去。还有一个容易被忽略的点CocoaPods。部分 iOS SDK 的集成方式不是直接拖 framework而是通过 Pod 引入。如果你机器上没装 CocoaPods到了那一步会卡住。检查方式很简单终端敲pod --version没有就装一下。后面用到的时候再说细节。真机测试是躲不掉的。很多 SDK 在模拟器上能初始化但不支持支付甚至直接提示“请使用真机测试”。所以准备一台 iPhone 或者 iPad至少系统版本不要太古老。2.2 SDK 包、demo 工程和文档从小7手游开发者后台下载 iOS 版 SDK 包下载下来一般是一个压缩包。解压后你会看到一个比较标准的目录里面有 framework 或者 .a 静态库有头文件有接入文档可能还有一个现成的 demo 工程。这里我要给出第一条最重要的建议先别管自己的项目先花半小时把 demo 工程跑起来。demo 工程就是 SDK 方给你写好的“标准答案”。它把初始化、登录、支付、回调都串好了你能在上面直接看到 SDK 的 API 怎么调、回调方法长什么样。很多开发者接SDK不爱看 demo直接翻文档就开始改自己的工程结果 API 名字抄错、参数位置搞反卡一整天。把 demo 跑通之后你心里对 SDK 的“脾气”就有数了再回来操作自己的工程完全是降维打击。2.3 一个适合导出的 Unity 工程Unity 工程这边最好切换到 iOS 平台再导出。提前确认几件事项目的 Bundle Identifier 确定下来没有这个后面全是坑目标 SDK 选 Device 还是 Simulator脚本后端用 IL2CPP 还是 Mono。我建议用 IL2CPP这是 Unity iOS 的默认选项性能和兼容性更好虽然首次编译时间长一点。工程里如果挂了一堆用不上的第三方插件导出前可以先禁掉。插件越多iOS 编译失败的概率越高特别是那些带原生代码的插件可能和 SDK 产生符号冲突。我遇到过某个广告插件和渠道 SDK 同时用了一个类名直接链接崩溃。另外在 Unity 里提前建一个空物体名字就叫 “PlatformBridge”挂一个 C# 脚本这个脚本专门接收 SDK 回调。记得在脚本的 Start 里调一下DontDestroyOnLoad(gameObject)防止场景切换时被销毁。这个细节后面会讲但你先做上。2.4 开工前先看三样东西打开 SDK 的接入文档别看全文直接找三样东西。第一初始化接口。找到 SDK 初始化的方法签名看一下参数列表。通常会有 appId、appKey 这类东西有的还要 bundleId。先把参数含义和顺序抄到本子上。第二回调协议。SDK 一般会定义一个 delegate 或者 block里面写着登录成功、登录失败、支付成功、支付取消这些方法。你要做的是把这些方法和 Unity 侧的回调方法一一对应起来。第三demo 里的目录结构。看看官方 demo 里怎么引用 framework、怎么配置 Info.plist、有没有额外链接系统库。照抄 demo 的配置永远比你自己猜要快。这三样东西搞明白等于拿到了一张地图。剩下的路虽然长但不会走错方向。3. Unity工程和Xcode工程的两端准备3.1 Unity 端导出 iOS 工程在 Unity 里打开 Build Settings平台切到 iOS点 Switch Platform。首次切换会花一段时间做平台相关导入。然后打开 Player Settings把 Bundle Identifier 改成你申请好的包名Other Settings 里确认 Target SDK 选的是 Device SDK。点击 Build 之后选择导出的目录Unity 会生成一个 iOS 原生工程。这个目录里有很多东西Classes、Libraries、Data 这些文件夹不用管你主要打开的是 Unity-iPhone.xcodeproj或者如果你装了 CocoaPods 并且工程里用了 Pod就要打开 Unity-iPhone.xcworkspace。有一个很常见的困惑新版 Unity 导出的工程里找不到传统意义上的 AppDelegate.mm。在 Unity 2019.3 之后AppDelegate 被封装到了 UnityFramework 内部你直接改主 target 的入口文件可能没用。很多 SDK 接入文档根本没更新这一点所以如果你按老教程找不到文件别怀疑自己换个思路可以直接把初始化代码放到 Unity 侧的桥接代码里由 C# 来触发初始化后面我会给出具体写法。3.2 在 Xcode 里集成 SDK打开 Xcode 工程之后把下载好的 SDK framework 拖到左侧工程导航栏里。拖的时候注意弹窗里勾选 “Copy items if needed”还要确保 target 勾选的是你的主 App target不是 UnityFramework。拖进去之后到 Build Phases 里找到 Link Binary With Libraries确认 framework 出现在列表里。很多朋友在这里踩坑framework 虽然在左边能看到了但实际链接阶段根本没参与编译最后编译报一堆Undefined symbol。为什么因为只拖文件没有把它加进 target。检查这个列表是最快的方式。如果 SDK 官方要求用 CocoaPods 集成那就简单了。工程根目录创建一个 Podfile写上 SDK 对应的 pod 名然后终端执行pod install。完成后不再打开 xcodeproj而是打开 xcworkspace。Pod 方式的好处是依赖库会自动链接不用手拖对新手反而更友好。还需要留意 SDK 对系统 framework 的依赖。常见的有 Security.framework、AdSupport.framework、StoreKit.framework 等。如果编译报错说缺少某个类或者某个符号多半就是少了系统依赖库照着报错信息到 Link Binary With Libraries 里加进去就行。3.3 Info.plist 配置调通的关键一步iOS 比 Android 多一个“权限开关集中营”就是 Info.plist。渠道 SDK 接入过程中至少有两处必须配置。第一处是网络安全。iOS 9 之后默认只允许 HTTPS如果 SDK 的服务器接口走 HTTP请求会被系统拦下来表现为初始化失败或者登录超时。接入文档一般会给出 ATS 配置代码片段最省事的是在 Info.plist 里加NSAppTransportSecurity然后把NSAllowsArbitraryLoads设为 YES。不过注意上架前最好把它收紧成只放行 SDK 的域名否则审核有风险。第二处是 URL Scheme。支付完成之后iOS 需要通过 URL Scheme 把用户从收银台拉回 App。这就像商店里付完钱之后给你开一张回程票没有这张票玩家付完钱就回不了游戏只能手动切后台。配置路径是 Info.plist 里加 CFBundleURLTypesURL Schemes 填 SDK 后台给你的标识。有些 SDK 还要求权限描述文案比如用到相册、日历或者广告标识符都要在 Info.plist 里写清楚用途单纯调用但不给描述文案App 会直接闪退注意看文档。3.4 桥接代码Unity与原生互调的“两条腿”桥接这块是很多人觉得最神秘的地方其实拆开就两段代码。第一段C# 调用原生。在 C# 脚本里声明一个 extern 方法固定写法是加上[DllImport(__Internal)]。__Internal是 Unity 的约定表示这个符号从 iOS 可执行文件内部找也就是你自己写的原生代码。using System.Runtime.InteropServices; public class PlatformBridge : MonoBehaviour { [DllImport(__Internal)] private static extern void q7_InitSDK(string appId, string appKey); [DllImport(__Internal)] private static extern void q7_Login(); }名字和参数都不是固定的只是演示用法。真实调用时以 SDK 头文件里的函数名为准。后面需要新建一个 Objective-C 文件把这些函数真正实现出来。比如这样#import Foundation/Foundation.h #include UnityInterface.h extern C { void q7_InitSDK(const char *appId, const char *appKey) { NSString *aid [NSString stringWithUTF8String:appId]; NSString *akey [NSString stringWithUTF8String:appKey]; // 这里调用 SDK 的初始化方法 } void q7_Login(void) { // 这里调起 SDK 登录 } }extern C是为了让 C 链接器认得这些符号UnitySendMessage 要从UnityInterface.h或者UnityFramework/UnityFramework.h里引入具体看你的 Unity 版本。老版本在 Classes 目录里新版本在 UnityFramework 里导入路径不对编译会报错。第二段原生调 C#。登录成功后SDK 的原生回调 delegate 里拿到数据转成 JSON 字符串用 UnitySendMessage 丢回 Unity。- (void)loginSuccess:(NSDictionary *)data { NSData *jsonData [NSJSONSerialization dataWithJSONObject:data options:0 error:nil]; NSString *jsonStr [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; UnitySendMessage(PlatformBridge, OnLoginResult, [jsonStr UTF8String]); }UnitySendMessage的三个参数分别是 GameObject 名字、脚本里的方法名、参数字符串。Unity 侧接收时方法必须是 public参数类型是 stringpublic void OnLoginResult(string json) { Debug.Log(登录回调: json); }这一来一回就完成了后面所有登录、支付、退出等事件都是同一个套路。理解了这两个方向桥接部分就彻底通了。4. 核心流程实现初始化、登录、支付、上报4.1 初始化启动后第一时间要做的事SDK 的初始化一般放在 App 启动阶段。如果你不想改 AppDelegate 这种原生入口可以在 Unity 的第一个场景里调用但要注意调用时机必须足够早。我踩过的一个坑是在某个游戏项目里我在登录按钮点击之后才调用初始化结果 SDK 没有足够时间恢复登录态玩家每次打开游戏都要手动重新登录。后来改成场景加载完成之后立刻调用初始化问题就消失了。初始化的参数一般是 appId、appKey有的还需要渠道号或者分包号。这些参数在小7手游后台申请游戏的时候会生成属于固定配置。为了让参数更安全也可以选择在原生侧硬编码但为了方便换环境和排查问题我更喜欢通过 C# 传过去。初始化之后SDK 会回调一个结果。务必把初始化失败的情况接住不要只是打一条日志就完了。我见过有的 App 初始化失败之后没提示玩家进来点登录没反应还以为是 Bug。要把它当成启动流程的一部分失败就弹窗提示甚至阻断后续操作。4.2 登录一条回调就走完了登录流程看起来流程多实际上就三步。第一步C# 调用原生登录方法SDK 弹出自己的登录页面。第二步玩家登录完成SDK 原生层拿到一个包含 token、userId 等信息的回调。第三步原生层把信息转成字符串丢回 UnityUnity 再把这个 token 发给自己的游戏服务器。这里必须强调客户端拿到的 token 不能直接当“登录成功”信物。正确的做法是把它发给你的后端由后端拿着 token 去小7服务器的接口换票验证验证通过才算真正的登录成功。说白了客户端回调只表示“玩家在 SDK 那边通过了认证”但你的服务器必须自己确认一遍否则别人伪造一个 token 就能冒充玩家。登录回调还要处理 success、fail、cancel 三种情况。很多新手只写 success忘记 cancel。玩家在登录页面点取消是一件非常正常的事你的 UI 应该把取消也视为一种退出流程而不是卡住动不了。另外登录按钮的点击要加锁在登录尚未结束前禁止再次调起不然连续点击可能出现两个登录页叠加的诡异现象。有的 SDK 启动时会自动恢复上次登录状态也就是静默登录。这种回调出现得很早可能还没到你点登录按钮的时候。开发者经常忽略这个回调导致玩家已经“登录过”了代码还傻傻停在未登录界面。接入文档里的“自动登录”部分一定要单独找出来对接。4.3 支付客户端回调只是“提示”发货要看服务端支付流程比登录多一点但核心链路不外乎五个环节。先是玩家发起购买你的客户端请求游戏服务器创建一个订单。服务器返回订单号、商品信息、金额等。然后是客户端拿着这些参数调起 SDK 支付玩家付款完成后客户端会收到一个支付成功的回调。最后你的服务器还会收到支付服务器发来的异步通知。这里有一条铁律客户端收到支付成功回调不要立刻发货顶多用来刷新 UI 或者播放一个动画。真正的发货动作必须由服务端收到支付服务器的异步通知并验签成功之后再做。为什么因为客户端的回调可以做假。懂点逆向的人可以伪造支付成功消息然后美滋滋不花钱拿到钻石。另外还有掉单问题客户端回调丢失了如果服务端流程健壮照样能通过异步通知补发。支付是资金相关的事情没有道理去信任一个客户端发来的消息。支付这边常见的问题反而不是 SDK 接入而是服务器端对重复通知处理得不好。支付服务器可能因为网络原因重复推送同一个订单的通知你的服务端要做幂等处理对同一个订单号的回调只发货一次。4.4 数据上报与生命周期接入最容易漏掉的部分很多人接完登录支付就认为完事了每次都被平台运营追着问“为什么不数据不对”才发现漏了上报。SDK 的业务数据上报至少有这么几类初始化完成、账号登录、角色创建、进入游戏、角色升级、充值到账、退出游戏。每个 SDK 的名字不一样接口参数也不一样但共同特点是“你需要在对应的时机主动调一次”。角色上报这个坑最隐蔽。有的渠道要求你在创建角色、切换服务器、角色升级的时候都要上报漏一次平台侧的玩家画像就不准。你没上报角色信息后面的付费分析、流失分析全是错的。接入文档里角色相关的接口尽量全部接完整。生命周期方面iOS SDK 往往需要你把 App 的前后台切换、即将进入后台、从后台恢复这些事件转发给它。举个具体例子玩家在支付过程中退到后台再切回来如果 SDK 不感知这个变化支付回调可能会一直等不到。一般在原生桥接文件里重写这几个方法把事件转发给 SDKapplicationDidBecomeActiveapplicationWillResignActiveapplicationDidEnterBackgroundUnity 导出的工程里这些方法要么写在 AppDelegate 里要么写在 UnityAppController 里。新版本 Unity 里你可能改不到可以在 Unity 侧用OnApplicationFocus和OnApplicationPause这两个生命周期回调去通知原生层绕开直接改 AppDelegate 的麻烦。5. 接完之后这些坑几乎人人都踩5.1 真机装包失败的排查清单SDK 接入好了编译通过结果包装不上手机是最折腾人的。这时候大概率不是 SDK 问题而是签名问题。真机安装失败常见就四类。第一类提示无法安装但没具体原因先检查描述文件里有没有包含这台设备开发阶段设备 UDID 没登记就会这样。第二类提示“未受信任的开发者”这是新装企业证书或者当天首次装开发包的正常现象到设备设置里信任一下证书就行。第三类安装完了马上闪退优先怀疑 framework 链接不完整检查刚才说的 target 配置。第四类一直卡在“正在安装”大概率是包太大或者 Xcode 版本和系统版本不兼容重启设备再试。我每次遇到装包问题第一件事不是翻代码而是把 Xcode 的 DerivedData 清掉重新编译。~/Library/Developer/Xcode/DerivedData这个目录删掉不心疼很多奇怪的链接问题清掉缓存就好。5.2 SDK初始化失败先查这三处初始化是第一个回调如果连它都失败后面全白搭。我的排查顺序固定是三个检查点。第一个检查点framework 有没有真正参与链接。光拖进去不算去 Build Phases 里看看或者直接看编译命令里有没有出现这个 framework 的名字。我遇到过一次framework 在工程导航里能看到但 target 成员关系没勾编译报了接近三十个未定义符号全是同一个 SDK 的类名。第二个检查点Bundle Identifier 是否和 SDK 后台配置一致。渠道后台创建游戏时会记录包名你本地改了包名SDK 的服务器校验不过去初始化就失败。测试阶段最容易出现这种问题因为要换证书换包名换来换去就变了。第三个检查点网络通不通。初始化请求被 ATS 拦截或者测试网络环境访问不了 SDK 服务器都会表现为失败。到 Info.plist 里先加NSAllowsArbitraryLoads看看能不能好能好就是 ATS 问题。还有一个万能排查法打开 Xcode 的控制台翻日志。SDK 一般会打自己的日志前缀比如 q7、SDK、IAP 之类的关键字。你搜这些关键字往往能看到比文档更有价值的错误信息。5.3 回调不触发UnitySendMessage里的地雷回调不触发是接入阶段最让人抓狂的问题因为你不知道它是没收到还是收到了没找到人。第一颗地雷GameObject 名字对不上。UnitySendMessage 的字符串比较是严格区分大小写的你在原生代码里写platformBridgeUnity 场景里叫PlatformBridge回调就永远找不到。第二颗地雷方法不是 public或者方法不在挂载的脚本上。UnitySendMessage 只能调 public 方法非 public 方法直接静默忽略。第三颗地雷目标 GameObject 被销毁了。场景一切换原来的物体没了回调自然丢进虚空。解决方式前面说了DontDestroyOnLoad而且要确保这个对象在第一个场景就被创建出来不要等到登录界面才创建。第四颗地雷回调 JSON 里带中文编码处理不当导致 Unity 解析失败。原生侧转成 NSString 再用UTF8String传入一般没问题但如果发现 C# 侧收到字符串后乱码优先怀疑这里。遇到回调问题我习惯在两端同时打日志。原生侧 NSLog 一条“即将发送回调xxx”Unity 侧 Debug.Log 一条“收到回调xxx”两端一对就知道问题出在发送之前还是接收之后。这一步通常五分钟内能定位。5.4 iOS 14 之后的隐私弹窗与IDFA如果你的 SDK 接入了广告归因或者数据统计可能会用到 IDFA。iOS 14 之后读取 IDFA 之前必须通过 AppTrackingTransparency 向用户申请授权不申请或者用户拒绝IDFA 返回的全是 0归因和统计会受到影响。实现方式是在 Info.plist 里加NSUserTrackingUsageDescription写一句用户能看懂的文案。然后在代码里请求#import AppTrackingTransparency/AppTrackingTransparency.h [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { // 请求完成 }];有一点要拎清这个弹窗跟登录支付没有关系。很多开发者以为不申请 IDFA 就登录不了其实不是。登录走的是账号体系不需要 IDFA。你只要根据 SDK 文档判断要用到这个权限的场景按需申请就行。别把隐私弹窗和登录流程绑死有些渠道的 SDK 文档会让你启动时统一申请那你就照文档做。5.5 审核与合规别在最后一步翻车接入渠道SDK之后如果这套包还要走正常的应用审核路径有几条红线别碰。不要动态下发可执行代码。iOS 对应用内加载脚本代码管得严热更方案如果绕不过审核风险很高。渠道 SDK 一般也不让你这么干老实按常规方式更新发版。权限声明要跟实际匹配。你声明要用相册结果审查发现代码里根本没有相册相关功能这就是风险点。接入 SDK 时如果连带引入了某些权限描述最好核对一下是不是真的需要。还有一点不要因为急着上渠道就修改 SDK 的加密和校验逻辑。有的开发者觉得 SDK 的签名校验很烦或者登录流程弹窗太多就想着绕过去。渠道 SDK 后台有防篡改校验改了包大概率会被识别出来轻则功能异常重则结算出问题得不偿失。6. 最后分享几个让接入顺畅的“土办法”这些办法不高级但真的能救命。第一个拿到 SDK 先跑官方 demo别管你对自己多自信。demo 是标准答案你后面的接入都是为了向它对齐。我在一个项目里因为没跑 demo自己摸索初始化参数摸了一天半后来打开 demo 对照发现是自己把参数顺序弄反了。第二个把回调方法名写死不动。接入之前先在 C# 脚本里把所有回调方法定义好打印到记事本上再去原生代码里写 UnitySendMessage。不要今天叫 OnLoginSuccess明天觉得别扭改成 OnLoginComplete两头各改一次早晚漏一处。第三个一次只改一个变量改完立即出包验证。最常见的事故是同时改了包名、换了框架、加了权限结果出问题之后根本分不清是哪一步导致的。每一次改动都能对应到一个可验证的结果排查成本会指数下降。接入 iOS SDK 这件事做到最后你会发现它更像一个流程管理问题而不是编程问题。环境配合、回调接上、签名搞定剩下的大多数时间都在打日志和对照文档。只要把流程理顺冷静一项项排查它真的没有你想的那么难。