Lua与ObjC的交互

lua 调用 C Lua中调用C函数(lua-5.2.3) 由sf0501创建,小路依依 最后一次修改2016-11-16 Lua可以调用C函数的能力将极大的提高Lua的可扩展性和可用性。 对于有些和操作系统相关的功能,或者是对效率要求较高的模块,我们完全可以通过C函数来实现,之后再通过Lua调用指定的C函数。 对于那些可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即typedef in... 阅读详情

1. 写在前面

很多时候我们都需要借助一些脚本语言来为我们实现一些动态的配置,那么就会涉及到如何让脚本语言跟原生语言交互的问题。平时在网上看得比较多的是使用JS(JavaScript)与iOS原生代码ObjC交互的文章。因为JS的解析器是iOS内部提供的(可以使用UIWebView或者JavaScriptCore.framework实现),所以使用JS来交互会感觉比较方便。

但是在这里,我想跟大家分享另外一种脚本语言的交互方式,就是使用Lua与原生的ObjC语言进行交互。Lua是一种轻量级的脚本语言,它的脚本解析器很小,编译出来只有100多kb,因此,作为一个内嵌的脚本解析器是首选的;而且Lua除了提供基本的脚本语言特性和系统功能外(IO读写),没有多余的功能性框架(JS解析器因为要配合Web的功能实现带有很多的工具库),这也是它轻量的表现。同时,提供了丰富的C Api来让其它的语言对其功能进行扩展,能够真正做到按需定制。

那么,这里所说到的C Api就是用于与ObjC交互的重点。因为ObjC本来就是C语言的超集,所以能够很方便的调用这些C Api,下面将一步一步实现交互的过程。

2. 下载和编译Lua解析器

首先,跳转到Lua官网的下载页将源码下载下来。然后解压下载包可以得到如下图所示的目录结构:

Lua源码目录结构

对应的目录说明如下表:

名称说明
docLua相关的文档,包括了编译文档、接口文档等
Makefile编译Lua使用,在这里我们不使用它来进行编译
README关于Lua的说明文件
srcLua的源码文件

3. 编译Lua源码

在这里我们只需要src目录中的源码文件,先打开src目录,将Makefile、lua.c、luac.c三个文件删除掉,需要说明的是lua.c和luac.c文件是用于编译生成lua和luac两个命令不属于解析器的功能,如果不删除可能会导致XCode无法编译通过。

接下来打开XCode创建一个新的项目并把src目录拖入项目中。如下图所示:

导入Lua源码到项目

然后Command+B进行编译,提示编译成功!

4. Lua C Api 与 栈

在开始实现Lua与OC交互之前先来了解两个非常重要的概念,一个是Lua的C Api,Lua的脚本解析器是使用C语言来编写的(基于C语言的源码跨平台特性,使得Lua可以在各种系统下面使用),因此它提供了丰富的C语言定义的接口来访问和操作Lua中的所有元素,掌握这些C Api可以更加灵活和方便地扩展Lua的功能,下面的交互实现正是使用这些C Api进行实现的。

另外一个就是栈 的概念,在Lua和C进行交互数据的时候会用到了一个栈的结构,栈中的每个元素都能保存任何类型的Lua值。要获取Lua中的一个值时,需要调用一个C Api函数,Lua就会将特定的值压入栈中,然后再通过相应的C Api将值取出来,如图所示:

C/C++获取Lua值

同样,要将一个值传给Lua时,需要先调用C Api将这个值压入栈,然后再调用C Api,Lua就会获取该值并将其从栈中弹出。 如图所示:

C/C++给Lua传值

这种设计方式主要是为了多种编程语言中统一数据交互中的存取方式,并且方便Lua中的垃圾回收机制的检测。

有了上述所说的概念,下面正式进入主题。

5. 初始化Lua环境

Lua环境的维护需要一个叫lua_State的结构体来支持,其贯穿了整个执行过程。因此,要使用Lua则需要先初始化一个lua_State结构体。修改ViewController的代码如下:

#import "lua.h"
#import "lauxlib.h"
#import "lualib.h"

@interface ViewController ()

@property (nonatomic) lua_State *state;     //定义一个lua_State结构

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.state = luaL_newstate();    //创建新的lua_State结构体
    luaL_openlibs(self.state);        //加载标准库
}

@end

6. 关于栈操作的C Api

上面说到数据“栈”的概念,C Api中提供了很多操作栈的功能接口,通常可以分为四大类:入栈操作、查询操作、取值操作和其他操作。

6.1 入栈操作

表示要将本地的某个类型的值放到数据栈中,然后提供给Lua层来获取和操作。该类操作接口有如下定义:

void lua_pushnil (lua_State *L);
void lua_pushnumber (lua_State *L, lua_Number n);
void lua_pushinteger (lua_State *L, lua_Integer n);
const char *lua_pushlstring (lua_State *L, const char *s, size_t len) ;
const char *lua_pushstring (lua_State *L, const char *s);
const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp);
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
void lua_pushboolean (lua_State *L, int b);
void lua_pushlightuserdata (lua_State *L, void *p);
int lua_pushthread (lua_State *L);

从上面的接口方法定义可以看出来,不同的Lua类型对应着不通的入栈接口,包括了整型(Integer)、布尔类型(Boolean)、浮点数(Number)、字符串(String)、闭包(Closure)、用户自定义数据(Userdata)、空类型(Nil)以及线程(Thread)。需要注意的是,C Api没有提供直接入栈Table类型的接口(估计是该数据类型无法与本地结构进行对应),如果需要入栈一个Table类型,可以使用lua_createtable方法来入栈一个Table,调用该方法会在栈顶放入一个Table的引用。

可见,假如我们需要在原生代码中给Lua的一个全局变量a赋一个整型值,那么可以如下面代码的做法:

lua_pushinteger (state, 1);
lua_setglobal (state, "a");

其中的lua_setglobal方法为设置全局变量的值,该方法会把数据栈顶的元素放入该方法第二个参数所指定的变量名对应的变量中,同时移除栈顶元素。如图:

lua_setglobal示意图

6.2 查询操作

之前说到栈中的每个元素都可以为任意类型,那么,对于如何判断元素的类型就可以通过该类方法来实现。该类方法的定义如下:

int lua_isnil (lua_State *state, int index);
int lua_isboolean (lua_State *state, int index);
int lua_isfunction (lua_State *state, int index);
int lua_istable (lua_State *state, int index);
int lua_islightuserdata (lua_State *state, int index);
int lua_isthread (lua_State *state, int index);
int lua_isnumber (lua_State *L, int idx);
int lua_isinteger (lua_State *L, int idx);
int lua_iscfunction (lua_State *L, int idx);
int lua_isstring (lua_State *L, int idx);
int lua_isuserdata (lua_State *L, int idx)

同样查询操作也是提供了不同的方法来检测不同的类型。其中第二个参数表示要检测类型的元素处于栈中的哪个位置。

关于栈中位置在lua中有两种形式表示,第一种是正数表示法,1表示栈底元素(即最先入栈的元素),然后越往上的元素,索引值越大。另外一种是负数表示法,-1表示栈顶元素(即最后入栈的元素),然后越往下的元素,索引值越小。如图所示:

栈索引两种表示方法示意图

lua_isXXX系列方主要是判断栈中数据是否能够被转换为对应数据类型时使用,如lua_isstring方法则是判断栈中某个元素是否能够被转换为string类型,所以当栈中数据为number类型时,其返回值也为true。

如果要进行非转换的强类型判断,可以使用lua_type方法来获取栈中元素的类型,然后根据类型来获取值。如判断栈顶元素的类型:

switch(lua_type(state, -1))
 {
    case LUA_TNUMBER:
        break;
    case LUA_TSTRING:
        break;
    case LUA_TBOOLEAN:
        break;
    case LUA_TUSERDATA:
        break;
    default:
        break;
}

6.3 取值操作

栈中的所有元素的获取都是通过该类方法来实现,通常该类方法跟在查询类方法后,当知道某个数据类型后,则调用对应数据类型的取值方法来获取元素。其方法定义如下:

int lua_toboolean (lua_State *L, int idx);
const char *lua_tolstring (lua_State *L, int idx, size_t *len);
lua_CFunction lua_tocfunction (lua_State *L, int idx);
void *lua_touserdata (lua_State *L, int idx);
lua_State *lua_tothread (lua_State *L, int idx);
const void *lua_topointer (lua_State *L, int idx);
lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum);
lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum);

取值操作的接口也相当简单,分别传入lua_State对象和栈索引即可。如果在调用时指定的类型跟栈中类型不同也不会有什么问题,接口会因为类型不正确而返回0或者NULL。

要注意的是该系列接口跟lua_isXXX系列接口一样,会对原始的类型进行转换输出,因此在做一些跟类型相关的操作时,最好时先判断类型再根据类型调用该方法取值,否则会导致一些意想不到的异常,如下面例子:

//存在targetVar = 1111;
lua_getglobal(self.state, "targetVar");
    
NSString *type = nil;
if (lua_isstring(self.state, -1))
{
  const char *str = lua_tostring(self.state, -1);
  NSLog(@"targetVal to string = %s", str);
}

switch (lua_type(self.state, -1))
{
  case LUA_TNIL:
    type = @"Nil";
    break;
  case LUA_TTABLE:
    type = @"Table";
    break;
  case LUA_TNUMBER:
    type = @"Number";
    break;
  case LUA_TSTRING:
    type = @"String";
    break;
  case LUA_TTHREAD:
    type = @"Thread";
    break;
  case LUA_TFUNCTION:
    type = @"Function";
    break;
  case LUA_TBOOLEAN:
    type = @"Boolean";
    break;
  case LUA_TUSERDATA:
    type = @"Userdata";
    break;
  case LUA_TLIGHTUSERDATA:
    type = @"Light Userdata";
    break;
  default:
    type = @"Unknown";
    break;
}

NSLog(@"targetVar is %@", type);

上面例子中的原意是要输出下面的内容:

targetVal to string = 1111
targetVar is Number

但是实际上却是这样:

targetVal to string = 1111
targetVar is String

这是由于使用了lua_tostring把栈中的targetVar的值改变了导致的,所以类似这样的操作一定要谨慎。

6.4 其他操作

使用上述3部分的操作可以满足与栈中数据进行交互的大多数情况。如果需要更加灵活地对栈进行操作,例如拷贝栈中某个元素,交互栈中元素位置等等的操作可以使用下面所定义的接口:

int lua_gettop (lua_State *L);
void lua_settop (lua_State *L, int idx);
void lua_pushvalue (lua_State *L, int idx);
void lua_remove(lua_State *L, int idx);
void lua_insert(lua_State *L, int idx);
void lua_replace(lua_State *L, int idx);
void lua_pop(lua_State *L, int n);

其中lua_gettop为获取栈顶位置,也即是栈中元素的个数,其实这个方法在处理原生方法的传入参数时很有用,可以确认传入参数的个数。有时候也可以用它来输出各个状态下的栈元素变化,来确认自己在操作栈时是否存在问题。

lua_settop方法用于设置栈顶位置,如果新栈顶高于之前的栈顶则会push一些nil的元素来填充;如果新栈顶低于之前的栈顶则会丢弃新栈顶之上的所有元素。如图所示:

lua_settop示意图

lua_pushvalue方法表示将栈中某个元素的副本压入栈顶。之前的栈元素不会发生变动。如图所示:

lua_pushvalue示意图

lua_remove方法用于移除指定索引上的元素,然后再该元素之上的所有元素会下移填补空缺(即元素的索引会发生变更)。如图所示:

lua_remove示意图

lua_insert会将指定索引位置之上的所有元素上移来开辟一个新的位置。然后将栈顶元素插入到该位置。如图所示:

lua_insert示意图

lua_replace方法会先弹出栈顶元素,然后将该元素覆盖到指定索引位置上。如图所示:

lua_replace示意图

lua_pop方法会从栈顶弹出指定数量的元素。如图所示:

lua_pop示意图

了解了上面的栈操作方法后,下面就是要结合这些方法来实现交互的实际操作。

7. From OC to Lua

7.1 空值传递

使用lua_pushnil方法可以将任意一个Lua变量置空。如:

lua_pushnil();
lua_setglobal(self.state, "val");

7.2 数值的传递

使用lua_pushinteger或者lua_pushnumber方法来将OC中的数值类型传递到Lua中指定的某个变量。如:

//传递整型值
lua_pushinteger(self.state, 1024);
lua_setglobal(self.state, "intVal");

//传递浮点型
lua_pushnumber(self.state, 80.08);
lua_setglobal(self.state, "numVal");

7.3 布尔值的传递

使用lua_pushboolean方法来实现,如:

lua_pushboolean(self.state, YES);
lua_setglobal(self.state, "boolVal");

7.4 字符串的传递

使用lua_pushstring方法可以传递字符串给Lua,要注意的是该方法接收的是一个c描述的字符串(即 char*)。如:

lua_pushstring(self.state, @"Hello World".UTF8String);
lua_setglobal(self.state, "stringVal");

7.5 二进制数组的传递

二进制数组在Lua中其实与字符串的存储方式相同,但是OC中不能直接使用lua_pushstring来进行二进制数组的传递,可以使用lua_pushlstring方法来传递。如:

char bytes[13] = {0xf1, 0xaa, 0x12, 0x56, 0x00, 0xb2, 0x43, '\0', '\0', 0x00, 0x90, 0x65, 0x73};
lua_pushlstring(self.state, bytes, 13);
lua_setglobal(self.state, "bytesVal");

7.6 方法的传递

Lua中只能接受C定义的方法传入,并且方法的声明必须符合lua_CFunction函数指针的定义,即:

int functionName (lua_State *state);

那么,传入方法则需要先定义一个C语言声明的方法,如:

int printHelloWorld (lua_State *state)
{
    NSLog(@"Hello World!");
    return 0;
}

方法里面简单地进行了一下信息打印,其中方法的返回值是一个整数,表明了该方法需要返回多少个值到Lua中(后续章节会进行返回值的相关演示),现在不需要返回值则为0。然后,再通过lua_pushcfunction方法将方法传入:

lua_pushcfunction(self.state, printHelloWorld);
lua_setglobal(self.state, "funcVal");

操作完成后,在Lua中就可以直接调用了:

funcVal();

如果定义的方法是允许接受参数的,那么可以从state参数里面获取传入的参数。拿上面的例子,例如方法接收一个名字的字符串参数,函数的代码则可以修改为:

int printHelloWorld (lua_State *state)
{
    if (lua_gettop(state) > 0)
    {
        //表示有参数
        const char *name = lua_tostring(state, 1);
        NSLog(@"Hello %s!", name);
    }
    
    return 0;
}

然后在Lua中则可以这样调用:

funcVal ("vimfung");

如果定义的方法不是直接打印字符串,而是组合了字符串给Lua返回,那么定义的方法里面则需要配合’lua_pushXXXX’系列方法来进行返回值传递。**需要注意的是:方法中return的数量要与push到栈中的值要一致,否则可能出现异常。**那么,上面定义的函数可以做如下修改:

int printHelloWorld (lua_State *state)
{
    if (lua_gettop(state) > 0)
    {
        //表示有参数
        const char *name = lua_tostring(state, 1);
        
        //入栈返回值
        NSString *retVal = [NSString stringWithFormat:@"Hello %s!", name];
        lua_pushstring(state, retVal.UTF8String);
        
        return 1;
    }
    
    return 0;
}

然后在Lua中则可以这样调用:

local retVal = funcVal("vimfung");
print(retVal);

7.7 数组和字典的传递

在Lua中,数组(Array)和字典(Dictionary)都由一个Table类型所表示(在Lua看来数组其实也属于一种字典,只是它的key是有序并且为整数)。如:

-- 定义数组
local arrayVal = {1,2,3,4,5,6};
-- 定义字典
local dictVal = {a=1, b=3, c=4, d=5};

上面的例子分别用了不带key的声明和带key的声明两种方式来创建Table类型。其中不带key的声明方式,解析器会默认为其创建一个key,该key是从1开始,由小到大进行分配,其等效于:

local arrayVal = {1=1, 2=2, 3=3, 4=4, 5=5, 6=6};

当然,两种方式是可以混合使用,如:

local tbl = {1, 2, a=1, b=2, 3};

Table属于比较复杂的数据结构,因此提供操作它的C Api也比较复杂,下面将根据数组和字典分别讲述它们的传递方式。

7.7.1 数组传递

首先,需要将一个Table类型入栈,这样才能对其进行进一步的操作。由于没有pushtable这样的方法,但是可以使用lua_newtable来创建一个Table对象,并且该对象会自动放入栈顶位置。如:

lua_newtable(self.state);

然后对要传递的数组进行遍历,并通过lua_rawseti方法将元素值设置到Table中。如:

NSArray *array = @[@1, @2, @3, @4, @5, @6];
 [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
       
    NSInteger value = [obj integerValue];
    lua_pushinteger(self.state, value);
    lua_rawseti(self.state, -2, idx + 1);
        
}];

lua_getglobal(self.state, "arrayVal");

通过上面的代码就可以把一个数组传递给arrayVal变量。值得注意的是:lua_rawseti方法表示要栈顶的元素设置给指定的Table对象的指定索引。其中的第二个参数是指Table对象在栈中的位置,第三个参数是表示在Table中的索引,一般索引是从1开始算起,因此上面代码中的idx需要加1。经过这样的操作后,栈顶的元素会被移除。如下图所示:

lua_rawseti示意图

7.7.2 字典传递

字典的传递同样需要先入栈一个Table:

lua_newtable(self.state);

然后对要传递的字典进行遍历,并通过lua_setfield方法将元素设置到Table中。如:

[dict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
    NSInteger value = [obj integerValue];
    lua_pushinteger(self.state, value);
    lua_setfield(self.state, -2, key.UTF8String);
        
}];
    
lua_setglobal(self.state, "dictVal");

lua_setfield与lua_rawseti功能类型,都是把一个元素放入Table中,只是一个用于指定整数索引,一个是指定字符串索引。通过上面的方式就可以把字典传递给Lua了。

7.8 自定义数据传递

Lua中一个比较强大的地方是它可以将任意的类型(包括类对象)进行传递。特别是在提供原生处理方法时,需要用到一些特定的数据类型作为参数时,Lua就可以帮我们实现这一块的传递。

要想传递自定义的数据则必须要使用Lua提供的Userdata类型。该类型有两种引用方式,一种是强引用Userdata,由Lua的GC来负责该类型变量的生命周期。另外一种是弱引用Userdata,又称Light Userdata,该类型不被GC所管理,其生命周期由原生层来决定。下面来看一下两种方式是如何实现的。

首先我们来定义一个OC的User类:

@interface User : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation User

@end

然后,利用lua_newuserdata方法来创建一个强引用Userdata,并创建一个User对象赋值给新建的Userdata。如:

void *instanceRef = lua_newuserdata(self.state, sizeof(User *));
instanceRef = (__bridge_retained void *)[[User alloc] init];
lua_setglobal(self.state, "userdataVal");

通过上面的代码就可以把User类实例封装成Userdata再传递给Lua。如果你要传递的对象并不需要Lua来管理生命周期,那么就可以创建一个弱引用的Userdata,如:

User *user = [[User alloc] init];
lua_pushlightuserdata(self.state, (__bridge void *)(user));
lua_setglobal(self.state, "userdataVal");

下面来看一个比较实际的例子,假设有一个提供给Lua调用的原生接口printUser,该接口会打印传入进来的用户信息,代码如下:

static int printUser (lua_State *state)
{
    if (lua_gettop(state) > 0)
    {
        //表示有参数传入
        User *user = (__bridge User *)(lua_topointer(state, 1));
        NSLog(@"user.name = %@", user.name);
    }
    
    return 0;
}

该方法通过lua_topointer方法来获取了一个Userdata数据类型并转换为User类实例对象然后打印其名称。接下来将其导出给Lua:

lua_pushcfunction(self.state, printUser);
lua_setglobal(self.state, "printUser");

然后生成一个User对象,并调用该方法传入该用户对象。如:

//创建User对象
User *user = [[User alloc] init];
user.name = @"vimfung";

lua_getglobal(self.state, "printUser");
//传入User参数
lua_pushlightuserdata(self.state, (__bridge void *)(user));
lua_pcall(self.state, 1, 0, 0);

上面的代码就是使用C Api来调用Lua的方法(下面的章节会详细讲述这块内容),通过OC代码创建了一个User对象并将其作为了参数传给了Lua的printUser方法。最终的输出信息如下:

user.name = vimfung

从OC到Lua的所有类型的转换和交互基本上都涉及到了,下面章节将会详细描述从Lua到OC上的一些交互和数据交换。

8. From Lua to OC

8.1 获取数值

通过lua_tonumber方法可以获取某个数值变量的值。如:

lua_getglobal(self.state, "aa");
double value = lua_tonumber(self.state, -1);
NSLog(@"aa = %f", value);
lua_pop(self.state, 1);

上述代码中的lua_getglobal方法是用于获取全局变量的值,调用它会把一个值放入栈顶。然后再通过lua_tonumber把栈顶的值读取出来并打印。需要注意的是通过lua_getglobal获取得到的值,在栈中是不会自动清除,因此,在用完某个变量时记得把它从栈中清除掉,代码中是通过lua_pop把值弹出栈的。

8.2 获取布尔值

与获取数值相同,通过lua_toboolean方法来获取某个布尔变量的值。如:

lua_getglobal(self.state, "aa");
BOOL value = lua_toboolean(self.state, -1);
if (value)
{
  NSLog(@"aa = YES");
}
else
{
  NSLog(@"aa = NO");
}
lua_pop(self.state, 1);

8.3 获取字符串

lua_tostring方法来获取字符串变量的值。如:

lua_getglobal(self.state, "aa");
const char *value = lua_tostring(self.state, -1);
NSLog(@"aa = %s", value);
lua_pop(self.state, 1);

8.4 获取二进制数组

lua_tolstring方法来获二进制数组变量的值。如:

lua_getglobal(self.state, "aa");
size_t len = 0;
const char *bytes = lua_tolstring(self.state, -1, &len);
NSData *data = [NSData dataWithBytes:bytes length:len];
NSLog(@"aa = %@", data);
lua_pop(self.state, 1);

8.5 方法的获取和调用

一般情况下,要获取Lua中的某个Function主要是用于对其进行调用。假设有一个Lua方法定义如下:

function printHelloWorld ()
  print("Hello World");
end

那么,对应OC中需要下面的代码来获取和调用它:

lua_getglobal(self.state, "printHelloWorld");
lua_pcall(self.state, 0, 0, 0);

上述代码中的lua_pcall方法表示将栈中的元素视作Function来进行调用。其中第二个参数为传入参数的数量,必须与压栈的参数数量一致;第三个参数为返回值的数量,表示调用后其放入栈中的返回值有多少个。第四个参数是用于发生错误处理时的代码返回。其运行原理如下图所示:

lua_pcall原理示意图

对于带参数和返回值的方法,在获得方法对象后,需要调用lua_pushXXX系列方法来设置传入参数。可以参考下面例子:

假设有一个加法的Lua方法,其定义如下:

function add (a, b)
  return a + b;
end

那么,OC中则可以进行下面操作来调用方法并传递参数,最终取得返回值然后打印到控制台:

lua_getglobal(self.state, "add");
lua_pushinteger(self.state, 1000);
lua_pushinteger(self.state, 24);
    
lua_pcall(self.state, 2, 1, 0);
    
NSInteger retVal = lua_tonumber(self.state, -1);
NSLog(@"retVal = %ld", retVal);

8.6 Table的获取和遍历

Table的获取跟其他变量一样,一旦放入栈后可以根据需要通过调用lua_getfield方法来指定的key的值。如:

//假设Lua中有一个Table变量aa = {key1=1000, key2=24};
lua_getglobal(self.state, "aa");
lua_getfield(self.state, -1, "key2");
NSInteger value = lua_tonumber(self.state, -1);
NSLog(@"value = %ld", value);
lua_pop(self.state, 1);

如果Table是声明时没有指定key,那么则需要调用lua_rawgeti来获取Table的值。如:

//假设Lua中有一个Table变量aa = {1000, 24};
lua_getglobal(self.state, "aa");
lua_rawgeti(self.state, -1, 2);
NSInteger value = lua_tonumber(self.state, -1);
NSLog(@"value = %ld", value);
lua_pop(self.state, 1);

有时候,Table存储的信息会在函数体外被访问,那么我们需要对Table进行遍历然后把它放入一个字典中,然后提供给程序使用。代码如下:

//假设Lua中有一个Table变量aa = {1000, 24};
lua_getglobal(self.state, "aa");
lua_pushnil(self.state);
while (lua_next(self.state, -2))
{
  NSInteger value = lua_tonumber(self.state, -1);
        
  if (lua_type(self.state, -2) == LUA_TSTRING)
  {
    const char *key = lua_tostring(self.state, -2);
    NSLog(@"key = %s, value = %ld", key, value);
  }
  else if (lua_type(self.state, -2) == LUA_TNUMBER)
  {
    NSInteger key = lua_tonumber(self.state, -2);
    NSLog(@"key = %ld, value = %ld", key, value);
  }
        
  lua_pop(self.state, 1);
}

上述代码利用lua_next方法来遍历Table的所有元素,该方法从栈顶弹出一个元素作为遍历Table的起始Key,然后把每个元素的Key和Value放入栈中。为了遍历所有元素所以起始的Key设置了一个nil值,证明要从Table最开始的Key进行遍历。如图:

lua_next原理示意图

值得注意的是,在获取Key值时,最好先判断Key的类型,然后再根据其对应类型调用相应的lua_toXXX方法。否则,因为lua_toXXX系列方法会对元素值进行类型转换,如整型的Key被lua_tostring转换为String后再给到lua_next进行遍历就会报找不到指定Key的错误。

8.7 获取自定义数据

利用lua_topointer方法来获取自定义数据,如:

lua_getglobal(self.state, "ud");
NSObject *obj = (__bridge User *)(lua_topointer(state, 1));

9. 结语

Lua虽然小巧,但五脏俱全。最主要的是它提供了丰富又强大的C Api接口允许我们进行高度的定制和扩展。可利用这些扩展特性开发适合自己的一套脚本引擎。基于上面所讲的,鄙人开发了一个LuaScriptCore的开源桥接框架,简化Lua与各种平台的交互,有兴趣的同学可以了解一下。

最后,希望通过本篇文章能够让大家对OC与Lua之间的交互有更近一步的了解,创造更多的可能性。

iOS集成Lua:动态化架构、热更新与桥接实战指南 脚本语言作为轻量级、可动态执行的逻辑层,在现代移动开发中扮演着重要角色。其核心原理在于通过虚拟机解释执行,与原生代码分离,实现逻辑的动态加载与更新。这一技术为应用带来了显著价值:提升开发迭代效率、支持热更新绕过审核、增强架构灵活性。在游戏玩法、活动配置、业务规则等频繁变动的场景中尤为适用。本文聚焦于在iOS平台集成Lua这一轻量高效的脚本语言,深入探讨如何通过桥接技术实现Swift/Objective-C与Lua的高效双向通信,并构建支持热更新与安全沙箱的工程化方案,以解决动态化与逻辑频繁变更的核心痛点。 阅读详情

相关推荐

百元DIY手机荧光计:低成本荧光检测平台搭建与实战

荧光检测是一种基于物质受特定波长光激发后发射更长波长荧光的分析技术,其核心原理在于光与物质的相互作用。通过选择性激发与检测,该技术能实现对痕量物质的高灵敏度、高特异性定量分析,在生物医学、环境监测和食品安全等领域具有重要价值。传统荧光分光光度计虽精度高,但成本昂贵、体积庞大且操作复杂,限制了其在现场快检和普及教育中的应用。本文聚焦于如何利用智能手机的高性能摄像头和处理器,结合低成本光学滤光片和LED光源,构建一个便携、经济的DIY荧光检测平台。该方案通过简化光路设计,采用带通滤光片进行波长选择,并利用图像处

weixin_34194829的博客 308

CCLuaObjcBridge - Lua 与 Objective-C 互操作的简单解决方案

月初的时候,发了一篇关于 Lua 与 Java 互操作的文章,里面提到了我创建的 LuaJavaBridge 工具。现在,最新的 Lua 与 Objective-C 互操作工具也出来了。因为是专门针对 cocos2d-x 的,所以命名为 CCLuaObjcBridge。 PS: 以前的 LuaJavaBridge 也会改名为 CCLuaJavaBridge,并且参考现在 CCLuaObj

天行健,君子以自强不息 1101

【第三只眼:Oracle通过归档日志查看系统的业务高峰sql】

根据oracle归档的业务高峰量,查找业务sql,进行优化

霍然的专栏 579

Object-C 和 lua的相互调用

本文主要讲如何完成lua和object-c的相互调用。       lua是一种脚本语言,可以方便的移植到各种宿主语言中,并且可以支持热更新,在游戏开发中也能当做主要的语言来编写游戏的逻辑,但是要接入第三方(例如支付宝等)时需要在不同的平台上(比如Android和ios)分别接入,这是就需要oc和lua完成通信 首先cocos2d 对oc和lua调用进行了封装,这就更有利于我们调用 c

小宇文 5720

瑞芯微RK3568开发板实战:Android11系统parameter.txt文件配置全解析(附常见问题排查)

本文深入解析了瑞芯微RK3568开发板在Android 11系统下parameter.txt文件的配置要点与实战技巧。作为BootLoader启动阶段的核心蓝图,该文件决定了硬件初始化、分区布局及电源管理。文章详细拆解了身份标识、GPIO控制(PWR_HLD)、命令行参数及分区表等关键字段的配置逻辑,并针对RK3568平台与Android 11特性提供了专项适配策略,最后附带了启动失败、分区异常等常见问题的深度排查方法。

php55的博客 896

Pyside6实战:3种.ui文件加载方式对比(附完整代码示例)

本文深入解析Pyside6中3种.ui文件加载方式的优劣与性能优化策略,包括动态加载QUiLoader、预编译方案及混合策略。通过实测数据和完整代码示例,帮助开发者根据项目需求选择最优解,提升界面加载效率与内存管理。

weixin_30897233的博客 90

cocos2d-x中lua与objective-c的互相调用

最近用cocos2d-x做项目,需要接第三方的sdk,ios端提供的api都是objective-c的,而我们是用lua作为脚本语言,所以需要学习一下lua对objective-c的调用,以及回调。 lua对objective-c的调用 开始的时候搜到了一个Lua-Objective-C-Br...

chenyexun4686727的博客 370

Abaqus子程序开发避坑指南:从UMESHMOTION到齿轮磨损分析实战

本文深入探讨Abaqus子程序开发中的关键技术与避坑指南,重点解析UMESHMOTION在齿轮磨损分析中的实战应用。从开发环境搭建、调试框架构建到性能优化和大规模计算,提供系统化的解决方案。通过真实案例展示如何避免内存泄漏、网格畸变等常见问题,并介绍数据驱动的磨损预测等前沿技术,帮助工程师提升仿真精度与效率。

weixin_30892037的博客 89

wax lua 简介

本文来自于: http://liuwei.so/post/2015/01/30/Wax-%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8.html 简介 Wax 是一个 Lua 与 Objective-C 桥接库。这意味着你可以在 Objective-C 中调用 Lua 代码,也可以在 Lua 中 调用 Objective-C 代码。 在 Lua 中访问

yangzm的专栏 814

从网口变压器到TVS管:手把手设计通过4KV浪涌的以太网电路

本文详细解析工业级以太网接口4KV浪涌防护设计,从网口变压器选型到TVS管布局,提供通过IEC61000-4-5标准测试的完整方案。重点介绍Bob Smith电路优化和PCB设计技巧,帮助工程师有效应对雷电感应和设备开关瞬态干扰,确保工业设备稳定运行。

study的专栏 286

jira bug 仪表盘

背景: 更直观管理bug,方便项目组内人员,创建、修改、关闭bug 使用的场景: 筛选器结果:可用来展示open、什么级别open的问题,方便督促开发快速解决问题 2维筛选器统计:统计经办人下bug状态是open,具体什么等级的数量。有利于开发可以调整工作进度 问题统计: 统计某版本,产出对应等级的bug数量,用于后期测试报告的编写 饼图:直观展示筛选人员的bug,例如想要看出每位测试人员产出bug比例 创建与解决的问题对比图: ...

chuanying_xiaoqi的博客 815

Lua学习链接

Lua捕获 Lua中_G 是个什么鬼 Lua与ObjC的交互 转载于:https://www.cnblogs.com/zsc1993/p/7867207.html

weixin_30807677的博客 72

CRC校验查表法详解

CRC校验的查表法理解算法原理基础计算查表法原理及计算查表法中的表CRC校验中数据逆序CRC校验中的其它参数 算法原理 CRC(Cyclic Redundance Check)循环冗余校验,是基于某个多项式计算出一组校验码,用于核对数据传输过程中是否被更改或传输错误 基础计算 假设需要检验的数据 g :10100111,这串二进制码可以表示为代数多项式g(x) = x^7 + x^5 + x^2 + x +1, 而作为除数的多项式 h(x) 可以自由选择或者按照国际标准选择,一般h(x)的阶数如果是m

boom的博客 1万+

Lua-Objective-C Bridge

Lua-Objective-C Bridge Lua-Objective-C Bridge 是一个开源项目,它提供了一个简洁的接口,使得 Lua 能够与 Objective-C 进行交互。 项目简介 Lua-Objective-C Bridge 的主要目的是为了让 Lua 开发者能够轻松地调用 Objective-C 的方法,从而在 Lua 中实现更复杂的业务逻辑。这个项目的实现方式是通过将 Lu...

gitblog_00053的博客 519

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

****************本文是Lua编写的游戏接入多盟SDK*******************

>>> 1.需要联网才可以看到测试接入广告效果(不知道为啥,反正没网看不到测试效果) 2.帮助文档上的-ObjC这个不要加了.直接跳过 3.导入多盟的SDK包,然后除了要加入多盟广告帮助文档PDF上一些库外,还要加入一些包,否则会有7个错误. 我是加了四个后7个错误就没了.如果加入四个还错,可粘贴错误信息到百度,一般都是少加了库. 炫耀加入的包为:MessageUI.framew

iserlohn_wind 745

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

探秘iOS开发中的Lua集成:轻松启航您的跨语言之旅

探秘iOS开发中的Lua集成:轻松启航您的跨语言之旅 在寻求软件灵活性和扩展性的道路上,将脚本语言融入原生应用已成为一种流行趋势。今天,我们来探索一款独特且实用的开源项目——一个展示了如何在iOS应用中无缝集成Lua语言的小巧示例。对于那些渴望通过脚本增强应用逻辑的开发者而言,这无疑是一个宝藏般的存在。 项目介绍 该项目是基于Swift编写的,旨在演示如何简单快捷地在iOS应用中嵌入Lua解释器。...

gitblog_00407的博客 577

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Lua 中用table表来实现类,继承,以及面向对象

Lua没有类这一说,但是可以使用表来接近实现类的功能,实现面向对象 1.创建一个文件ClassA,写objA类: 注意:类实际是一个table类型的表,这里是一个类用一个单独的lua文件来存放 1.1类中没有任何字段 给类添加二个方法 objA = {} function objA.play() print("objA.play&am

liaosheng:) 1975

在 iOS 应用中使用 Lua 作为模块粘合剂的方法

iOS 中应用的变化主要集中在三个方面:界面,业务逻辑,数据源。 下面是自己做的一个技术实验,可以用来处理业务逻辑的变化。 大概说一下,具体见代码。 使用Lua作为类的粘合剂,主要应用在什么场景呢?比如:点击一个按钮后,程序跳转到什么类或者控制器。 思路:向 Lua 提供完备的信息,通过在 Lua 中的配置,实现业务逻辑的整合。 推理下去,用 ObjC 代码写模块,模块间的粘合用 Lua

Proteas的专栏 1090

2dx-lua Other Linker Flags 设置成 -ObjC 真机编译报错"_GCControllerDidDisconnectNotification"

原文地址:http://www.cocoachina.com/bbs/read.php?tid=225869 用的 cocos2dx-lua quick3.3,Other Linker Flags 设置成 -ObjC 时候, 真机无法编译通过 报错如下: Undefined symbols for architecture armv7s:   "_GCControllerDidDisc

u010536615的专栏 3527

Setting the global LUA_PATH variable from C++/C?

Setting the global LUA_PATH variable from C++/C? https://stackoverflow.com/questions/4125971/setting-the-global-lua-path-variable-from-c-c I'm trying to set my global LUA_PATH variable directly from C/C++, I'm using Lua from my iPhone applications, so my

kuangben2000的博客 355

Lua - 2

__index

iosfighterlb - HeyYou 219

告别紧耦合!objc-dependency-visualizer帮你重构Objective-C项目

objc-dependency-visualizer是一款专为Objective-C和Swift项目打造的依赖关系可视化工具,它能帮助开发者清晰呈现项目当前状态,轻松识别类之间的耦合程度。无论你是刚接手一个遗留项目的新手,还是想优化现有代码结构的资深开发者,这款工具都能让项目依赖关系一目了然,让重构工作不再盲目。 ## 为什么需要依赖关系可视化? 随着项目规模的扩大,代码之间的依赖关系往往会变

gitblog_00158的博客 1002
上一篇: 使用LuaScriptCore来让Lua与iOS进行交互
下一篇: 使用Xcode开发iOS语法检查的Clang插件
杰嗒嗒的阿杰
博客等级 码龄15年 17粉丝 24原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值