LuaObjcBridge 用于在 Lua 与 iOS 原生层之间传递基础类型、表和 Lua 回调。本文面向使用 cocos.cocos2d.luaoc 的 Cocos2d-x 3.x Lua 工程;本次按官方 3.17.2 源码核对接口,不同分支的支持类型与文件路径可能不同。新增示例尚未经过 iOS 编译或真机验证。
交互专题:Lua 与 Java / Android 交互 · Lua 与 C++ 交互。
最小示例:luaoc 调用 Objective-C 类方法
luaoc.callStaticMethod 调用的是 + 类方法。先创建 LuaDemoBridge.mm 并加入 Xcode 的应用 Target;这个示例只做字符串回传,不需要相机权限或 UI:
1 | #import <Foundation/Foundation.h> |
在 iOS 的 Lua 逻辑中调用:
1 | if device.platform == "ios" then |
按接口约定,成功时 ok 为 true、ret 是 Hello, Lua;这不是本次的真机运行记录。Objective-C 类名是 LuaDemoBridge,不需要 Java 风格的包名;方法参数在 Lua 侧写 echo,传入 table 时桥接会自动追加 :,对应 +echo:。
无参方法应传 nil。空 table {} 仍表示一个字典参数:例如 CreateCamera 与 CreateCamera: 是不同的 selector,不能混用。
Lua table 与 NSDictionary 的对应关系
| Lua 参数值 | Objective-C 接收值 | 注意事项 |
|---|---|---|
| string | NSString | 适合消息、路径等文本 |
| number / boolean | NSNumber | 按业务读取数值或布尔值;转换细节以当前分支为准 |
| table | NSDictionary | 本文示例使用字符串键;不要当成任意原生对象或通用 NSArray 传递 |
| 顶层参数表中的 function | 存在 NSNumber 中的函数 ID | 用 intValue 取出;不是 Objective-C block |
官方 3.17.2 的嵌套 table 转换没有处理 function;回调函数应放在顶层参数表中。参数签名要使用一个 NSDictionary *,不要把多个 Lua 字段写成多个 Objective-C 参数。
local ok, ret = luaoc.callStaticMethod(...) 的 ok 必须检查。失败时 ret 是错误码;成功时则是返回值。void 方法成功返回的 ret 可以是 nil,不能用 ret ~= nil 代替成功判断。
iOS 回调 Lua:相机业务片段
Objective-C 类方法
下面的方法属于项目已有的 CameraManager,拍照业务仍需自行实现;完整相机接入见文末链接。示例只允许一个待完成请求,下面的变量放在实现文件作用域。
1 | static int luaCallbackFunc = 0; |
Lua 调用方法
1 | local OC_CLASSNAME = "CameraManager" |
上面的两个示例分别对应无参调用和传入 Lua 回调。原生层拿到的是函数 ID;单次请求完成后必须释放注册的引用,且只释放一次。以下代码放在 CameraManager.mm 的实现中,并在类声明中声明 execLuaFunc 和 finishLuaRequest:。代码使用 C++ lambda,不能放在普通 .m 文件中:
1 | #import "cocos2d.h" |
成功时可调用 [self execLuaFunc];失败和取消分别调用 [self finishLuaRequest:@"failed"]、[self finishLuaRequest:@"cancelled"]。Lua 的同一个回调据此处理状态,三条路径都会释放本次引用。结果字符串在排队前复制到 std::string,不会依赖异步执行时的 NSString 生命周期。
DoTakePic: 由 Lua 在 Cocos2d-x 线程调用,完成判定、取出 ID 和清零也排到该线程执行;其他线程只提交结果,不直接读写 luaCallbackFunc。此片段要求原生请求的完成回调至多触发一次,且没有实现并发请求管理。异步 API 若可能重复回调或让旧请求晚到,应使用请求 ID 与完成标记,不能只靠一个全局 luaCallbackFunc 判断当前请求。Lua 引擎退出后也不能再访问其栈。
生命周期与线程注意事项
- 原生侧若把
luaCallbackFunc存为全局变量,同时发起多个请求会互相覆盖。应按请求 ID 保存回调,或在发起下一次请求前拒绝旧请求。 - 无论成功、失败还是取消,都要在同一条完成路径调用
LuaBridge::releaseLuaFunctionById;遗漏会造成 Lua 函数引用无法释放。 - 从系统相机、相册等异步回调进入 Lua 时,应切换到 Cocos2d-x 可安全执行 Lua 的线程/时机,避免与渲染或 Lua 栈并发访问。
callStaticMethod返回的ok必须检查;类名、方法名或参数类型不匹配时不要继续按成功结果处理。
luaoc 错误码与排查表
下表对应 官方 CCLuaBridge.h 的枚举,具体返回位置见 CCLuaObjcBridge.mm。Android luaj 的相同数字含义可能不同。
| 错误码 | 含义 | 优先检查 |
|---|---|---|
-1 |
参数无效 | 类名和方法名是否为字符串;是否按 className, methodName, args 传参 |
-2 |
类未找到 | 类名拼写;.mm / .m 文件是否加入应用 Target;Objective-C 类是否进入应用二进制 |
-3 |
方法未找到 | 类方法 + 与实例方法 -;selector 名称;无参 nil 与有参 table 是否匹配 |
-4 |
Objective-C 异常 | Xcode 原生异常日志;字典字段类型;方法内业务代码是否抛出异常 |
-5 |
方法签名不可用 | 类方法是否存在;是否误传冒号;无参方法是否错传 {};参数是否为一个 NSDictionary * |
官方 3.17.2 在获取不到 methodSignatureForSelector 时返回 -5,因此方法写错也可能得到 -5,不能只凭 -3 判断“方法不存在”。
如果原生已收到函数 ID,之后却抛出异常,应检查 ID 的清理路径;ok == false 不表示桥接已替业务释放所有回调引用。成功、失败、取消均要清理已接管的 ID,并防止重复完成。
相关阅读
附录:luaoc 包装层与桥接注册
以下保留原文的包装层和注册源码及注释。Lua 入口是 cocos.cocos2d.luaoc.callStaticMethod;下方历史注释里的“包名.类名”是 Java 风格的说法,Objective-C 侧应使用实际运行时类名。
1 | local luaoc = {} |
同样的也是在OC层提供了一个LuaObjcBridge的类,注册了一个callStaticMethod的方法。
1 | void LuaObjcBridge::luaopen_luaoc(lua_State *L) |