ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Cocos Creator iOS微信登录接入实战:从原生桥接到Universal Links完整指南

Cocos Creator iOS微信登录接入实战:从原生桥接到Universal Links完整指南 微信登录接入尤其是在 Cocos Creator 里做 iOS 端说难不难但坑确实不少。最近正好在项目里把整套流程完整过了一遍从微信开放平台后台配置到 Xcode 工程里集成微信 SDK再到 Cocos 层的 JS 桥接踩了好几个有代表性的坑。这篇就把完整处理过程拆开讲清楚希望能让后面接手的人少走点弯路。这篇适合两类人一是 Cocos Creator 项目里要做微信登录但之前没接触过原生 iOS 开发的二是已经在 Android 端接好了微信登录现在补 iOS 端结果发现配置和调用方式跟 Android 完全不是一回事的人。iOS 端多了 Universal Links、Info.plist、签名关联域这些 Android 没有的概念这也是大多数人卡住的地方。好直接进入正题。1. 接入前的整体思考微信登录到底在做什么1.1 微信登录在 iOS 上的完整链路微信登录本质上还是走 OAuth 授权只不过官方把跳转和回调都封装进了 iOS 的 SDK 里也就是WechatOpenSDK。用户在游戏里点击“微信登录”按钮后Cocos 层通过桥接调用原生方法原生再调用微信 SDK把当前的 App 切到微信等用户确认授权后微信再通过 Universal Link 或者 URL Scheme 把用户带回游戏。整个链路的终点并不是拿到用户信息而是拿到一个临时授权码code。真正的用户信息换取必须由你的后端服务器去完成把code传给后端后端拿着code加上AppSecret去微信接口换取access_token和openid然后建立你自己的登录态。AppSecret绝对不能写进客户端这个是底线。SDK 在 iOS 端替我们解决的就是“跳过去、跳回来”的交互问题以及回传数据的完整性。所以如果你发现微信能跳过去但跳回来之后 Cocos 收不到结果那大概率不是业务逻辑问题而是原生处理回调的入口没配置好。1.2 Cocos Creator 接入的两种常见方式在 Cocos Creator 里接微信登录网上能搜到很多方案但归纳起来无外乎两种。第一种是直接用第三方封装插件很多插件市场里的原生扩展包已经封装好了微信登录、分享等功能。优点是省事拖进去就能用缺点是插件版本很容易滞后微信 SDK 一旦升级或者 Cocos Creator 版本升级插件就可能出现莫名问题。尤其近几年微信和多 provider 的 XCFramework 更新频繁插件报错后很难排查最后还是得自己看原生工程。第二种是自己写原生桥接层也就是在 iOS 原生工程里维护一个类负责微信 SDK 的注册和回调然后通过jsb.reflection让 Cocos 层调用原生方法。这种方案代码量不大但可控性最好而且不依赖第三方插件生命周期出问题也能迅速定位。我推荐团队项目用这一种。1.3 为什么我推荐“原生桥接 事件轮询”方案Cocos Creator 的 JS/TS 引擎跑在 JavaScriptCore/V8 之上嵌套在原生工程里调用原生方法通常用jsb.reflection.callStaticMethod来完成。但原生那边拿到微信回调后怎么把数据重新塞回 Cocos 的 JS 线程是很多人第一次写的时候最纠结的地方。一个常见做法是在 Objective-C 里直接执行 JS 函数类似调用引擎接口去 eval 字符串。这个方案能做但不同 Creator 版本、不同引擎版本之间API 名称和调用方式都有细微差异很容易出现编译过了但运行时崩或者回调丢失。我在自己项目里用的方案是“原生事件队列 JS 轮询”。原生微信回调到达后只把结果放进一个内存数组队列Cocos 层用一个定时器每隔 100ms 去拉一次。这个方案看起来“笨”但非常稳不受引擎版本影响也不存在线程切换问题。后面所有代码示例都是围绕这个方案写的。2. 微信开放平台与 iOS 工程的前置配置2.1 创建移动应用获取 AppID登录微信开放平台在“管理中心 - 移动应用 - 创建移动应用”填写 App 名称、简介、iOS Bundle ID 等信息。审核通过后你会拿到一个以wx开头的 AppID以及一个 AppSecret。AppID 是客户端要用的AppSecret 保存在服务器不能放客户端。这一步没有什么技术难度但有一个点很容易被忽略微信开放平台里填写的 Bundle ID 必须和你 Xcode 工程的 Bundle ID 完全一致有个别字母或者大小写不同后面 SDK 就会返回错误码。如果 App 已经在 App Store 上架最好把 App Store 应用 ID 也填上这样后续使用微信的其他能力会顺利一些。2.2 Universal Links最容易翻车的配置iOS 9 之后微信 SDK 的登录回调开始要求走 Universal Links这和 Android 的 URL Scheme 完全不同。简单理解Universal Links 是苹果提供的一种通过 HTTPS 链接唤起 App 的能力微信在授权完成后会访问一个你预先声明过的链接然后唤起你的 App。需要在三个地方做配置缺一不可。第一Apple Developer 后台在 App ID 的 Capabilities 里勾选 Associated Domains然后重新生成描述文件并下载安装。第二Xcode 工程在 Signing Capabilities 里添加 Associated DomainsDomain 格式是applinks:yourdomain.com。注意是applinks:前缀不是你实际的 URL。第三服务器验证文件你需要在域名对应的 HTTPS 根目录或.well-known目录下放置一个apple-app-site-association文件文件名没有任何后缀。内容类似这样{ applinks: { apps: [], details: [ { appID: TEAMID.com.yourcompany.gamename, paths: [*] } ] } }其中TEAMID是你的开发团队 IDcom.yourcompany.gamename是 Bundle ID。这个文件必须能通过https://yourdomain.com/apple-app-site-association直接访问不能有重定向。我犯过的一个低级错误是服务器把文件放在子目录下结果微信死活调不起 App。微信开放平台后台还有一个“iOS 应用”设置页需要填写 Universal Links格式通常要求以https://开头可以带路径比如https://yourdomain.com/wx/这个链接需要和你后面在代码里注册 SDK 时传入的universalLink保持一致。2.3 Xcode 工程里需要完成的预设置拿到 AppID、配置好 Universal Links 后进入 Xcode 工程本身还需要做几个准备。首先如果你手动集成 SDK需要把微信官方提供的WechatOpenSDK-XCFramework拖到工程的 Frameworks 里。如果使用 CocoaPods可以加一句pod WechatOpenSDK-XCFramework然后pod install。其次要配置 URL Types。在 Target - Info - URL Types 里添加一个 scheme格式是wx 你的 AppID例如wxd1234567890abcdef。这个是微信跳回 App 时的入口之一很多新手漏了这一项表现就是微信授权页弹出来但点确认后回不到游戏。然后还要在 Info.plist 里添加LSApplicationQueriesSchemes数组至少包含weixin和weixinULAPI。这个字段用来让系统知道你的 App 可以调起微信如果不加调用[WXApi isWXAppInstalled]时永远返回 NO。如果手动集成的是旧版静态库还建议在 Other Linker Flags 里加上-ObjC否则可能出现WXApi相关类没被加载的运行时异常。新版 XCFramework 一般不需要但加了也没坏处。另外很多版本的微信 SDK 不支持 Bitcode建议在 Build Settings 里把Enable Bitcode设为 NO。3. 在 Cocos Creator 里写原生桥接层3.1 原生侧 WeChatHelper 类的实现我习惯在构建出来的 iOS 工程里新建一个WeChatHelper类专门负责微信 SDK 的全部逻辑。这个类继承NSObject实现WXApiDelegate协议。先看头文件#import Foundation/Foundation.h #import WechatOpenSDK/WXApiObject.h interface WeChatHelper : NSObject WXApiDelegate (instancetype)sharedInstance; (void)registerWX; (void)sendAuthRequest; (BOOL)handleOpenURL:(NSURL *)url; (BOOL)handleUniversalLink:(NSUserActivity *)userActivity; (NSString *)pollAuthResponse; end实现文件里的关键代码我拆开说明。第一段是注册微信 SDKregisterWX会在 App 启动时被调用#import WeChatHelper.h #import WechatOpenSDK/WXApi.h static NSMutableArrayNSDictionary * *gAuthEventQueue; implementation WeChatHelper (instancetype)sharedInstance { static WeChatHelper *instance; static dispatch_once_t onceToken; dispatch_once(onceToken, ^{ instance [[WeChatHelper alloc] init]; gAuthEventQueue [NSMutableArray array]; }); return instance; } (void)registerWX { NSString *appId wx1234567890abcdef; NSString *universalLink https://yourdomain.com/wx/; [WXApi registerApp:appId universalLink:universalLink]; }第二个是发起登录请求。这里我们需要拿到当前的 UIViewController微信 SDK 会把它作为授权页的展示容器。如果拿不到 rootViewController会出现“能跳微信但无法弹起授权页”的情况 (void)sendAuthRequest { SendAuthReq *req [[SendAuthReq alloc] init]; req.scope snsapi_userinfo; req.state cocos_login_2024; UIViewController *vc [WeChatHelper topViewController]; if (!vc) { NSLog(WeChatHelper: topViewController is nil); return; } [WXApi sendAuthReq:req viewController:vc delegate:[WeChatHelper sharedInstance] completion:nil]; }然后是处理微信回调的两个入口。URL Scheme 和 Universal Link 都要接住 (BOOL)handleOpenURL:(NSURL *)url { return [WXApi handleOpenURL:url delegate:[WeChatHelper sharedInstance]]; } (BOOL)handleUniversalLink:(NSUserActivity *)userActivity { return [WXApi handleOpenUniversalLink:userActivity delegate:[WeChatHelper sharedInstance]]; }微信授权结果会走到onResp:方法。这里我把它转成一个字典放进事件队列等待 Cocos 层来取- (void)onResp:(BaseResp *)resp { if ([resp isKindOfClass:[SendAuthResp class]]) { SendAuthResp *authResp (SendAuthResp *)resp; NSDictionary *data { errCode: (authResp.errCode), code: authResp.code ?: , state: authResp.state ?: , lang: authResp.lang ?: , country: authResp.country ?: }; synchronized (gAuthEventQueue) { [gAuthEventQueue addObject:data]; } } }最后是给 Cocos 层提供的轮询接口。我返回的不是 NSDictionary而是 JSON 字符串这样 JS 侧解析起来最稳妥 (NSString *)pollAuthResponse { NSDictionary *data nil; synchronized (gAuthEventQueue) { if (gAuthEventQueue.count 0) { data gAuthEventQueue.firstObject; [gAuthEventQueue removeObjectAtIndex:0]; } } if (!data) return nil; NSData *jsonData [NSJSONSerialization dataWithJSONObject:data options:0 error:nil]; return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; }关于topViewController我习惯写一个递归查找从keyWindow的rootViewController往下找正在显示的控制器。这里不展开项目里一般都有类似工具方法。要注意的是 iOS 13 之后keyWindow的获取方式有变化建议通过connectedScenes找到UIWindowScene再拿windows免得在高版本系统上拿不到控制器。3.2 JS/TS 侧用轮询方式接收登录结果Cocos Creator 的脚本层不需要做什么特殊初始化核心就是两个调用发起登录、轮询结果。我封装了一个 TypeScript 工具类import { sys } from cc; export class WxLogin { public static sendAuthRequest(): void { if (!sys.isNative) { console.warn(wx login only works in native); return; } jsb.reflection.callStaticMethod(WeChatHelper, sendAuthRequest); } public static pollAuthResponse(): any { const result jsb.reflection.callStaticMethod(WeChatHelper, pollAuthResponse); if (result) { return JSON.parse(result); } return null; } public static requestLogin(): Promiseany { return new Promise((resolve, reject) { const timer setInterval(() { const data this.pollAuthResponse(); if (data) { clearInterval(timer); if (data.code) { resolve(data); } else { reject(data); } } }, 100); setTimeout(() { clearInterval(timer); reject({ errCode: -2, message: wx login timeout }); }, 15000); this.sendAuthRequest(); }); } }业务侧用起来就很简单登录按钮点击后WxLogin.requestLogin() .then((res: any) { // res.code 是临时授权码交给后端去换 token console.log(wx login success, res.code); }) .catch((err: any) { console.log(wx login failed, err); });这里有两个细节值得注意。第一个pollAuthResponse只取一条事件所以每次轮询如果返回为空就继续等待拿到结果后立即清掉定时器避免多个登录请求并发时互相污染。第二个微信登录通常不能直接在模拟器里测因为模拟器上没有微信客户端所以一定要连真机调试。3.3 将桥接代码接进 AppDelegateCocos 构建出来的原生工程里iOS 的入口文件通常叫AppDelegate.mm或者AppController.mm。你需要在这个文件里做三处修改。第一处application:didFinishLaunchingWithOptions:里调用微信注册- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 引擎初始化等原有代码 [WeChatHelper registerWX]; return YES; }第二处处理 URL Scheme 回调- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionaryUIApplicationOpenURLOptionsKey,id *)options { if ([WeChatHelper handleOpenURL:url]) { return YES; } return [SomeExistingRouter handleURL:url]; }第三处处理 Universal Link 回调尤其是在 App 已经运行用户从微信授权页通过 Universal Link 跳回来时- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArrayidUIUserActivityRestoring * _Nullable))restorationHandler { if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { if ([WeChatHelper handleUniversalLink:userActivity]) { return YES; } } return [[OtherRouter shared] handleUserActivity:userActivity]; }如果你的工程用了 SceneDelegate那还需要在scene:continueUserActivity:以及scene:openURLContexts:里加上对应处理不然 iOS 13 之后的设备在收到回调时入口根本走不到AppDelegate。Cocos 默认模板一般不启用 SceneDelegate但有些团队接入了其他第三方框架后打开了这个功能容易忽略。4. 从构建到真机调试的完整操作流程4.1 快速验证联调直接改构建出来的 Xcode 工程对大多数中小项目来说最省事的方式是先让功能跑通再考虑工程化。具体步骤如下。第一步在 Cocos Creator 里选择构建 iOS 平台。构建完成后找到生成目录比如 3.x 通常在build/ios/proj/ios下2.x 一般在build/jsb-link/frameworks/runtime-src/proj.ios用 Xcode 打开.xcodeproj。第二步把微信 SDK 集成进去。如果用 CocoaPods直接在 Podfile 里加依赖如果暂时不想引入 Pod就手动将微信提供的.xcframework拖到 Frameworks 组里并在 Build Phases 里确认它没有被遗漏。第三步按前面第 2 节说的配置 Associated Domains、URL Types、Info.plist然后在 AppDelegate 里接入注册代码。第四步连上真机运行。这里要特别提醒Cocos Creator 如果再次构建到同一个输出目录会有概率把原生工程覆盖掉。所以一旦你开始手动修改 Xcode 工程后面尽量不要再对着同一个目录反复构建。如果 Cocos 侧改了资源或逻辑可以用 Creator 单独导出 js 资源和配置或者构建到一个新目录再手动把热更资源同步过去。这个流程虽然原始但在功能联调阶段足够用。我见过不少团队在这里卡住其实不是代码问题而是构建覆盖导致改动丢失白忙大半天。4.2 用 CocoaPods 管理微信 SDK如果你的 iOS 团队已经使用 CocoaPods建议直接用依赖管理。在 Podfile 里加platform :ios, 11.0 target 你的Target名 do pod WechatOpenSDK-XCFramework end然后执行pod install之后必须用.xcworkspace打开工程。这种方式的好处是微信 SDK 升级时只需要修改 Podfile 里的版本号不用手动拖 framework也避免因为 SDK 文件没拷全导致链接错误。有一个小坑Cocos Creator 构建出来的 Xcode 工程本身可能已经配置了搜索路径和链接脚本加了 Pod 后如果工程里同时存在手动拖入的旧微信 SDK会出现重复符号。所以加 Pod 之前最好先检查工程里有没有手动加过libWeChatSDK.a或WechatOpenSDK相关的文件有的话先删掉。4.3 打包上线前需要检查的清单功能调试通过后别急着打包上架先过一遍下面的检查表。检查项示例/预期值说明Bundle ID 匹配与微信后台一致不一致时授权会失败URL Typeswx AppID缺了跳回不了 AppAssociated Domainsapplinks:yourdomain.com缺了 Universal Link 不生效AASA 文件可访问https://yourdomain.com/apple-app-site-association返回 JSON且不能重定向微信后台 Universal Link与代码注册时一致两边不一致微信会无法回跳Info.plist 白名单weixin和weixinULAPI缺少则检测不到微信服务器换取 token使用code AppSecretAppSecret 不能在客户端授权登录超时页面停留无反应时给提示客户端最好做 15 秒超时还有一个很容易忽略的环节如果你为了省事把 AASA 文件的paths写成了[*]最好在上线前收紧。微信登录理论上只需要保留你填写的那个路径比如/wx/*。*在联调时好用但会让其他非预期路径也唤起 AppApp Store 审核时如果被抽查到可能被要求解释。5. 联调中的高频问题与排查方法5.1 微信不跳转SDK 返回 -1SDK 返回-1是通用错误几乎所有问题都可能会落出这个错误码。常见原因按出现频率排序第一AppID 不正确。检查代码里注册的 AppID 是否和微信开放平台完全一致wx后面的字母数字有没有复制错。这个低级问题非常常见我帮同事排查过好几次最后都是 AppID 抄错了。第二Universal Link 不一致。代码里registerApp:universalLink:传的链接和微信开放平台后台填写的链接以及 AASA 文件校验的路径三者只要对不上微信就会静默失败。第三没有安装微信客户端。模拟器上通常没有微信老版本 SDK 在无微信环境下调用授权会直接返回失败。真机上也要确认微信版本不要太旧部分老版本微信对新版 Universal Link 支持不完整。第四没有正确处理onResp回调。有些项目虽然调用了sendAuthReq但 delegate 参数传了别的对象或者 delegate 指向的对象提前释放授权结果到了却没人处理。建议排查时先加日志在registerWX、sendAuthRequest、onResp三个位置分别输出点标记能很快定位到底卡在哪一步。5.2 Universal Link 怎么都调不起来Universal Link 不生效可以从系统层和微信层两部分排查。系统层的自测方法是在备忘录或者浏览器地址栏里直接输入你的 Universal Link 地址长按后如果出现“在App中打开”说明系统已经识别到了。如果一直只是在浏览器打开说明 AASA 文件或 App 的 Associated Domains 配置有问题。这时候先检查服务器返回的文件格式是否正确。不要只看浏览器打开要执行curl -I https://yourdomain.com/apple-app-site-association curl -L https://yourdomain.com/apple-app-site-association-I看响应头确认 Content-Type 是 application/json且没有 301/302 跳转。-L看实际内容确认 appID 和 bundle id 配对正确。如果文件是中文编码或者 BOM都有可能导致解析失败。微信层的自测是走一次完整登录流程在 Xcode 里断点application:continueUserActivity:看微信跳回来时是否进到这个方法。如果这个方法根本没被调用那问题更可能在系统配置而不是微信 SDK。如果方法被调用了但[WXApi handleOpenUniversalLink:userActivity delegate:]返回 NO那重点查注册链接和后台链接是否一致。5.3 授权成功但 code 为空或者回调不触发授权页面已经弹出用户也点确认了但跳回游戏后 code 是空。这种情况优先看SendAuthResp.errCode。如果errCode是-4说明用户主动取消了授权如果errCode是-2也是取消。这些都属于业务上的正常情况不需要特殊处理但你自己要能分清楚别把取消当成 bug。如果errCode是 0但code为空那基本可以确定是 SDK 版本或 Universal Link 配置异常。换个新版微信 SDK 再试同时确认registerApp:universalLink:传入的不是带http://的普通链接必须是https://。回调不触发则要重点检查 AppDelegate 的openURL方法返回值。有些工程里接了其他登录 SDK比如 QQ、支付宝会在同一个方法里做分发如果你的代码在里面直接return YES没再往下传给微信 SDK微信的回调就会丢失。正确做法是先让微信 SDK 判断能处理就返回 YES不能处理再走其他路由。我前面第 3.3 节的示例就是这么写的。6. 让接入过程更工程化的一点建议6.1 构建后自动注入代码而不是手动改来改去手动改 Xcode 工程在联调阶段没问题但对长期维护的项目来说风险太高。因为 Cocos Creator 每次重新构建 iOS 工程时都有可能覆盖手动改动而且你的团队里不是每个客户端同事都熟悉原生工程交接成本很高。比较推荐的做法是在 Cocos Creator 构建插件里挂一个 after-build 钩子用 Node 脚本去修改构建产物。脚本可以完成的事情包括往 AppDelegate 里写入微信注册代码、修改 Info.plist 添加白名单、给 Xcode 工程添加 Pods 依赖等等。虽然没有 JSPatch 那种热门但这类构建后处理在原生游戏项目里很常见能省掉大量重复劳动。如果团队没有精力写构建插件也可以退而求其次单独维护一个“iOS 原生壳工程”这个工程目录不放在 Cocos 构建目录下而是独立存在。每次 Cocos 侧发布只把更新后的assets包和src/main.js等资源同步过去原生改动始终保留在这个独立工程里。这种方式不自动化但至少不会因为覆盖而丢失配置。6.2 注意版本变化和官方 SDK 的更新微信 SDK 在 iOS 端的更新频率不算低近两年最主要的变化就是全面转向 XCFramework并且更强调 Universal Links。以前那种手动拖libWeChatSDK.a、配置Other Linker Flags的老流程仍然能跑但新版本 SDK 已经建议用官方 Cocoapods 或 Swift Package Manager 集成。Cocos Creator 本身也在迭代不同版本里jsb.reflection的调用方式大同小异但 3.x 的绑定层和 2.x 有明显差异。如果你在 3.x 里发现callStaticMethod调用不到原生方法先检查原生类有没有被编译进工程再检查 AppDelegate 里有没有提前注册。这个问题往往不是你写错了而是构建时原生代码没参与链接。我个人实践经验是接入微信登录这件事真正花时间的不是写代码而是理解 iOS 的配置链路。只要把 Universal Links、URL Types、白名单这几样前置配置吃透了剩下的代码只是体力活。希望这篇能把你的坑填掉一些后面接的时候少走弯路。
返回列表