1. 寫在前面
很多時候我們都需要借助一些腳本語言來為我們實現(xiàn)一些動態(tài)的配置隅忿,那么就會涉及到如何讓腳本語言跟原生語言交互的問題取试。平時在網(wǎng)上看得比較多的是使用JS(JavaScript)與iOS原生代碼ObjC交互的文章攻柠。因為JS的解析器是iOS內(nèi)部提供的(可以使用UIWebView或者JavaScriptCore.framework實現(xiàn))赃磨,所以使用JS來交互會感覺比較方便。
但是在這里轧钓,我想跟大家分享另外一種腳本語言的交互方式序厉,就是使用Lua與原生的ObjC語言進行交互。Lua是一種輕量級的腳本語言毕箍,它的腳本解析器很小弛房,編譯出來只有100多kb,因此而柑,作為一個內(nèi)嵌的腳本解析器是首選的文捶;而且Lua除了提供基本的腳本語言特性和系統(tǒng)功能外(IO讀寫),沒有多余的功能性框架(JS解析器因為要配合Web的功能實現(xiàn)帶有很多的工具庫)媒咳,這也是它輕量的表現(xiàn)粹排。同時,提供了豐富的C Api來讓其它的語言對其功能進行擴展涩澡,能夠真正做到按需定制顽耳。
那么,這里所說到的C Api就是用于與ObjC交互的重點筏养。因為ObjC本來就是C語言的超集斧抱,所以能夠很方便的調(diào)用這些C Api,下面將一步一步實現(xiàn)交互的過程渐溶。
2. 下載和編譯Lua解析器
首先辉浦,跳轉(zhuǎn)到Lua官網(wǎng)的下載頁將源碼下載下來。然后解壓下載包可以得到如下圖所示的目錄結(jié)構(gòu):
對應(yīng)的目錄說明如下表:
名稱 | 說明 |
---|---|
doc | Lua相關(guān)的文檔茎辐,包括了編譯文檔宪郊、接口文檔等 |
Makefile | 編譯Lua使用掂恕,在這里我們不使用它來進行編譯 |
README | 關(guān)于Lua的說明文件 |
src | Lua的源碼文件 |
3. 編譯Lua源碼
在這里我們只需要src目錄中的源碼文件,先打開src目錄弛槐,將Makefile懊亡、lua.c、luac.c三個文件刪除掉乎串,需要說明的是lua.c和luac.c文件是用于編譯生成lua和luac兩個命令不屬于解析器的功能店枣,如果不刪除可能會導(dǎo)致XCode無法編譯通過。
接下來打開XCode創(chuàng)建一個新的項目并把src目錄拖入項目中叹誉。如下圖所示:
然后Command+B進行編譯鸯两,提示編譯成功!
4. Lua C Api 與 棧
在開始實現(xiàn)Lua與OC交互之前先來了解兩個非常重要的概念长豁,一個是Lua的C Api钧唐,Lua的腳本解析器是使用C語言來編寫的(基于C語言的源碼跨平臺特性,使得Lua可以在各種系統(tǒng)下面使用)匠襟,因此它提供了豐富的C語言定義的接口來訪問和操作Lua中的所有元素钝侠,掌握這些C Api可以更加靈活和方便地擴展Lua的功能,下面的交互實現(xiàn)正是使用這些C Api進行實現(xiàn)的酸舍。
另外一個就是棧 的概念帅韧,在Lua和C進行交互數(shù)據(jù)的時候會用到了一個棧的結(jié)構(gòu),棧中的每個元素都能保存任何類型的Lua值父腕。要獲取Lua中的一個值時弱匪,需要調(diào)用一個C Api函數(shù),Lua就會將特定的值壓入棧中璧亮,然后再通過相應(yīng)的C Api將值取出來萧诫,如圖所示:
同樣,要將一個值傳給Lua時枝嘶,需要先調(diào)用C Api將這個值壓入棧帘饶,然后再調(diào)用C Api,Lua就會獲取該值并將其從棧中彈出群扶。 如圖所示:
這種設(shè)計方式主要是為了多種編程語言中統(tǒng)一數(shù)據(jù)交互中的存取方式及刻,并且方便Lua中的垃圾回收機制的檢測。
有了上述所說的概念竞阐,下面正式進入主題缴饭。
5. 初始化Lua環(huán)境
Lua環(huán)境的維護需要一個叫l(wèi)ua_State的結(jié)構(gòu)體來支持,其貫穿了整個執(zhí)行過程骆莹。因此颗搂,要使用Lua則需要先初始化一個lua_State結(jié)構(gòu)體。修改ViewController的代碼如下:
#import "lua.h"
#import "lauxlib.h"
#import "lualib.h"
@interface ViewController ()
@property (nonatomic) lua_State *state; //定義一個lua_State結(jié)構(gòu)
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.state = luaL_newstate(); //創(chuàng)建新的lua_State結(jié)構(gòu)體
luaL_openlibs(self.state); //加載標(biāo)準(zhǔn)庫
}
@end
6. 關(guān)于棧操作的C Api
上面說到數(shù)據(jù)“椖豢眩”的概念丢氢,C Api中提供了很多操作棧的功能接口傅联,通常可以分為四大類:入棧操作疚察、查詢操作蒸走、取值操作和其他操作。
6.1 入棧操作
表示要將本地的某個類型的值放到數(shù)據(jù)棧中貌嫡,然后提供給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類型對應(yīng)著不通的入棧接口衅枫,包括了整型(Integer)嫁艇、布爾類型(Boolean)、浮點數(shù)(Number)弦撩、字符串(String)、閉包(Closure)论皆、用戶自定義數(shù)據(jù)(Userdata)益楼、空類型(Nil)以及線程(Thread)。需要注意的是点晴,C Api沒有提供直接入棧Table類型的接口(估計是該數(shù)據(jù)類型無法與本地結(jié)構(gòu)進行對應(yīng))感凤,如果需要入棧一個Table類型,可以使用lua_createtable
方法來入棧一個Table粒督,調(diào)用該方法會在棧頂放入一個Table的引用陪竿。
可見,假如我們需要在原生代碼中給Lua的一個全局變量a賦一個整型值屠橄,那么可以如下面代碼的做法:
lua_pushinteger (state, 1);
lua_setglobal (state, "a");
其中的lua_setglobal方法為設(shè)置全局變量的值族跛,該方法會把數(shù)據(jù)棧頂?shù)脑胤湃朐摲椒ǖ诙€參數(shù)所指定的變量名對應(yīng)的變量中,同時移除棧頂元素锐墙。如圖:
6.2 查詢操作
之前說到棧中的每個元素都可以為任意類型礁哄,那么,對于如何判斷元素的類型就可以通過該類方法來實現(xiàn)溪北。該類方法的定義如下:
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)
同樣查詢操作也是提供了不同的方法來檢測不同的類型桐绒。其中第二個參數(shù)表示要檢測類型的元素處于棧中的哪個位置。
關(guān)于棧中位置在lua中有兩種形式表示之拨,第一種是正數(shù)表示法茉继,1表示棧底元素(即最先入棧的元素),然后越往上的元素蚀乔,索引值越大烁竭。另外一種是負數(shù)表示法,-1表示棧頂元素(即最后入棧的元素)乙墙,然后越往下的元素颖变,索引值越小生均。如圖所示:
lua_isXXX系列方主要是判斷棧中數(shù)據(jù)是否能夠被轉(zhuǎn)換為對應(yīng)數(shù)據(jù)類型時使用,如lua_isstring方法則是判斷棧中某個元素是否能夠被轉(zhuǎn)換為string類型腥刹,所以當(dāng)棧中數(shù)據(jù)為number類型時马胧,其返回值也為true。
如果要進行非轉(zhuǎn)換的強類型判斷衔峰,可以使用lua_type
方法來獲取棧中元素的類型佩脊,然后根據(jù)類型來獲取值。如判斷棧頂元素的類型:
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 取值操作
棧中的所有元素的獲取都是通過該類方法來實現(xiàn)垫卤,通常該類方法跟在查詢類方法后威彰,當(dāng)知道某個數(shù)據(jù)類型后,則調(diào)用對應(yīng)數(shù)據(jù)類型的取值方法來獲取元素穴肘。其方法定義如下:
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);
取值操作的接口也相當(dāng)簡單歇盼,分別傳入lua_State對象和棧索引即可。如果在調(diào)用時指定的類型跟棧中類型不同也不會有什么問題评抚,接口會因為類型不正確而返回0或者NULL豹缀。
要注意的是該系列接口跟lua_isXXX系列接口一樣,會對原始的類型進行轉(zhuǎn)換輸出慨代,因此在做一些跟類型相關(guān)的操作時邢笙,最好時先判斷類型再根據(jù)類型調(diào)用該方法取值,否則會導(dǎo)致一些意想不到的異常侍匙,如下面例子:
//存在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);
上面例子中的原意是要輸出下面的內(nèi)容:
targetVal to string = 1111
targetVar is Number
但是實際上卻是這樣:
targetVal to string = 1111
targetVar is String
這是由于使用了lua_tostring把棧中的targetVar的值改變了導(dǎo)致的,所以類似這樣的操作一定要謹慎想暗。
6.4 其他操作
使用上述3部分的操作可以滿足與棧中數(shù)據(jù)進行交互的大多數(shù)情況妇汗。如果需要更加靈活地對棧進行操作,例如拷貝棧中某個元素江滨,交互棧中元素位置等等的操作可以使用下面所定義的接口:
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
為獲取棧頂位置铛纬,也即是棧中元素的個數(shù),其實這個方法在處理原生方法的傳入?yún)?shù)時很有用唬滑,可以確認傳入?yún)?shù)的個數(shù)告唆。有時候也可以用它來輸出各個狀態(tài)下的棧元素變化,來確認自己在操作棧時是否存在問題晶密。
lua_settop
方法用于設(shè)置棧頂位置擒悬,如果新棧頂高于之前的棧頂則會push一些nil的元素來填充;如果新棧頂?shù)陀谥暗臈m攧t會丟棄新棧頂之上的所有元素稻艰。如圖所示:
lua_pushvalue
方法表示將棧中某個元素的副本壓入棧頂懂牧。之前的棧元素不會發(fā)生變動。如圖所示:
lua_remove
方法用于移除指定索引上的元素,然后再該元素之上的所有元素會下移填補空缺(即元素的索引會發(fā)生變更)僧凤。如圖所示:
lua_insert
會將指定索引位置之上的所有元素上移來開辟一個新的位置畜侦。然后將棧頂元素插入到該位置。如圖所示:
lua_replace
方法會先彈出棧頂元素躯保,然后將該元素覆蓋到指定索引位置上旋膳。如圖所示:
lua_pop
方法會從棧頂彈出指定數(shù)量的元素。如圖所示:
了解了上面的棧操作方法后途事,下面就是要結(jié)合這些方法來實現(xiàn)交互的實際操作验懊。
7. From OC to Lua
7.1 空值傳遞
使用lua_pushnil
方法可以將任意一個Lua變量置空。如:
lua_pushnil();
lua_setglobal(self.state, "val");
7.2 數(shù)值的傳遞
使用lua_pushinteger
或者lua_pushnumber
方法來將OC中的數(shù)值類型傳遞到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
方法來實現(xiàn)义图,如:
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 二進制數(shù)組的傳遞
二進制數(shù)組在Lua中其實與字符串的存儲方式相同碱工,但是OC中不能直接使用lua_pushstring
來進行二進制數(shù)組的傳遞,可以使用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
函數(shù)指針的定義,即:
int functionName (lua_State *state);
那么桶蛔,傳入方法則需要先定義一個C語言聲明的方法,如:
int printHelloWorld (lua_State *state)
{
NSLog(@"Hello World!");
return 0;
}
方法里面簡單地進行了一下信息打印漫谷,其中方法的返回值是一個整數(shù)仔雷,表明了該方法需要返回多少個值到Lua中(后續(xù)章節(jié)會進行返回值的相關(guān)演示),現(xiàn)在不需要返回值則為0舔示。然后碟婆,再通過lua_pushcfunction
方法將方法傳入:
lua_pushcfunction(self.state, printHelloWorld);
lua_setglobal(self.state, "funcVal");
操作完成后,在Lua中就可以直接調(diào)用了:
funcVal();
如果定義的方法是允許接受參數(shù)的惕稻,那么可以從state參數(shù)里面獲取傳入的參數(shù)竖共。拿上面的例子,例如方法接收一個名字的字符串參數(shù)俺祠,函數(shù)的代碼則可以修改為:
int printHelloWorld (lua_State *state)
{
if (lua_gettop(state) > 0)
{
//表示有參數(shù)
const char *name = lua_tostring(state, 1);
NSLog(@"Hello %s!", name);
}
return 0;
}
然后在Lua中則可以這樣調(diào)用:
funcVal ("vimfung");
如果定義的方法不是直接打印字符串公给,而是組合了字符串給Lua返回,那么定義的方法里面則需要配合'lua_pushXXXX'系列方法來進行返回值傳遞蜘渣。需要注意的是:方法中return的數(shù)量要與push到棧中的值要一致淌铐,否則可能出現(xiàn)異常。那么蔫缸,上面定義的函數(shù)可以做如下修改:
int printHelloWorld (lua_State *state)
{
if (lua_gettop(state) > 0)
{
//表示有參數(shù)
const char *name = lua_tostring(state, 1);
//入棧返回值
NSString *retVal = [NSString stringWithFormat:@"Hello %s!", name];
lua_pushstring(state, retVal.UTF8String);
return 1;
}
return 0;
}
然后在Lua中則可以這樣調(diào)用:
local retVal = funcVal("vimfung");
print(retVal);
7.7 數(shù)組和字典的傳遞
在Lua中腿准,數(shù)組(Array)和字典(Dictionary)都由一個Table類型所表示(在Lua看來數(shù)組其實也屬于一種字典,只是它的key是有序并且為整數(shù))拾碌。如:
-- 定義數(shù)組
local arrayVal = {1,2,3,4,5,6};
-- 定義字典
local dictVal = {a=1, b=3, c=4, d=5};
上面的例子分別用了不帶key的聲明和帶key的聲明兩種方式來創(chuàng)建Table類型吐葱。其中不帶key的聲明方式街望,解析器會默認為其創(chuàng)建一個key,該key是從1開始弟跑,由小到大進行分配灾前,其等效于:
local arrayVal = {1=1, 2=2, 3=3, 4=4, 5=5, 6=6};
當(dāng)然,兩種方式是可以混合使用窖认,如:
local tbl = {1, 2, a=1, b=2, 3};
Table屬于比較復(fù)雜的數(shù)據(jù)結(jié)構(gòu)豫柬,因此提供操作它的C Api也比較復(fù)雜,下面將根據(jù)數(shù)組和字典分別講述它們的傳遞方式扑浸。
7.7.1 數(shù)組傳遞
首先烧给,需要將一個Table類型入棧,這樣才能對其進行進一步的操作喝噪。由于沒有pushtable這樣的方法础嫡,但是可以使用lua_newtable
來創(chuàng)建一個Table對象,并且該對象會自動放入棧頂位置酝惧。如:
lua_newtable(self.state);
然后對要傳遞的數(shù)組進行遍歷榴鼎,并通過lua_rawseti
方法將元素值設(shè)置到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");
通過上面的代碼就可以把一個數(shù)組傳遞給arrayVal變量晚唇。值得注意的是:lua_rawseti
方法表示要棧頂?shù)脑卦O(shè)置給指定的Table對象的指定索引巫财。其中的第二個參數(shù)是指Table對象在棧中的位置,第三個參數(shù)是表示在Table中的索引哩陕,一般索引是從1開始算起平项,因此上面代碼中的idx需要加1。經(jīng)過這樣的操作后悍及,棧頂?shù)脑貢灰瞥?/strong>闽瓢。如下圖所示:
7.7.2 字典傳遞
字典的傳遞同樣需要先入棧一個Table:
lua_newtable(self.state);
然后對要傳遞的字典進行遍歷,并通過lua_setfield
方法將元素設(shè)置到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中,只是一個用于指定整數(shù)索引缨叫,一個是指定字符串索引椭符。通過上面的方式就可以把字典傳遞給Lua了。
7.8 自定義數(shù)據(jù)傳遞
Lua中一個比較強大的地方是它可以將任意的類型(包括類對象)進行傳遞弯汰。特別是在提供原生處理方法時艰山,需要用到一些特定的數(shù)據(jù)類型作為參數(shù)時,Lua就可以幫我們實現(xiàn)這一塊的傳遞咏闪。
要想傳遞自定義的數(shù)據(jù)則必須要使用Lua提供的Userdata類型曙搬。該類型有兩種引用方式,一種是強引用Userdata,由Lua的GC來負責(zé)該類型變量的生命周期纵装。另外一種是弱引用Userdata征讲,又稱Light Userdata,該類型不被GC所管理橡娄,其生命周期由原生層來決定诗箍。下面來看一下兩種方式是如何實現(xiàn)的。
首先我們來定義一個OC的User類:
@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation User
@end
然后挽唉,利用lua_newuserdata
方法來創(chuàng)建一個強引用Userdata滤祖,并創(chuàng)建一個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來管理生命周期匠童,那么就可以創(chuàng)建一個弱引用的Userdata,如:
User *user = [[User alloc] init];
lua_pushlightuserdata(self.state, (__bridge void *)(user));
lua_setglobal(self.state, "userdataVal");
下面來看一個比較實際的例子塑顺,假設(shè)有一個提供給Lua調(diào)用的原生接口printUser汤求,該接口會打印傳入進來的用戶信息,代碼如下:
static int printUser (lua_State *state)
{
if (lua_gettop(state) > 0)
{
//表示有參數(shù)傳入
User *user = (__bridge User *)(lua_topointer(state, 1));
NSLog(@"user.name = %@", user.name);
}
return 0;
}
該方法通過lua_topointer
方法來獲取了一個Userdata數(shù)據(jù)類型并轉(zhuǎn)換為User類實例對象然后打印其名稱严拒。接下來將其導(dǎo)出給Lua:
lua_pushcfunction(self.state, printUser);
lua_setglobal(self.state, "printUser");
然后生成一個User對象扬绪,并調(diào)用該方法傳入該用戶對象。如:
//創(chuàng)建User對象
User *user = [[User alloc] init];
user.name = @"vimfung";
lua_getglobal(self.state, "printUser");
//傳入User參數(shù)
lua_pushlightuserdata(self.state, (__bridge void *)(user));
lua_pcall(self.state, 1, 0, 0);
上面的代碼就是使用C Api來調(diào)用Lua的方法(下面的章節(jié)會詳細講述這塊內(nèi)容)裤唠,通過OC代碼創(chuàng)建了一個User對象并將其作為了參數(shù)傳給了Lua的printUser方法挤牛。最終的輸出信息如下:
user.name = vimfung
從OC到Lua的所有類型的轉(zhuǎn)換和交互基本上都涉及到了,下面章節(jié)將會詳細描述從Lua到OC上的一些交互和數(shù)據(jù)交換种蘸。
8. From Lua to OC
8.1 獲取數(shù)值
通過lua_tonumber
方法可以獲取某個數(shù)值變量的值赊颠。如:
lua_getglobal(self.state, "aa");
double value = lua_tonumber(self.state, -1);
NSLog(@"aa = %f", value);
lua_pop(self.state, 1);
上述代碼中的lua_getglobal
方法是用于獲取全局變量的值,調(diào)用它會把一個值放入棧頂劈彪。然后再通過lua_tonumber
把棧頂?shù)闹底x取出來并打印。需要注意的是通過lua_getglobal
獲取得到的值顶猜,在棧中是不會自動清除沧奴,因此,在用完某個變量時記得把它從棧中清除掉长窄,代碼中是通過lua_pop
把值彈出棧的滔吠。
8.2 獲取布爾值
與獲取數(shù)值相同,通過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 獲取二進制數(shù)組
lua_tolstring
方法來獲二進制數(shù)組變量的值。如:
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 方法的獲取和調(diào)用
一般情況下嚣潜,要獲取Lua中的某個Function主要是用于對其進行調(diào)用冬骚。假設(shè)有一個Lua方法定義如下:
function printHelloWorld ()
print("Hello World");
end
那么,對應(yīng)OC中需要下面的代碼來獲取和調(diào)用它:
lua_getglobal(self.state, "printHelloWorld");
lua_pcall(self.state, 0, 0, 0);
上述代碼中的lua_pcall
方法表示將棧中的元素視作Function來進行調(diào)用。其中第二個參數(shù)為傳入?yún)?shù)的數(shù)量只冻,必須與壓棧的參數(shù)數(shù)量一致庇麦;第三個參數(shù)為返回值的數(shù)量,表示調(diào)用后其放入棧中的返回值有多少個喜德。第四個參數(shù)是用于發(fā)生錯誤處理時的代碼返回山橄。其運行原理如下圖所示:
對于帶參數(shù)和返回值的方法,在獲得方法對象后舍悯,需要調(diào)用lua_pushXXX
系列方法來設(shè)置傳入?yún)?shù)航棱。可以參考下面例子:
假設(shè)有一個加法的Lua方法萌衬,其定義如下:
function add (a, b)
return a + b;
end
那么饮醇,OC中則可以進行下面操作來調(diào)用方法并傳遞參數(shù),最終取得返回值然后打印到控制臺:
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的獲取跟其他變量一樣奄薇,一旦放入棧后可以根據(jù)需要通過調(diào)用lua_getfield
方法來指定的key的值驳阎。如:
//假設(shè)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,那么則需要調(diào)用lua_rawgeti
來獲取Table的值馁蒂。如:
//假設(shè)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存儲的信息會在函數(shù)體外被訪問,那么我們需要對Table進行遍歷然后把它放入一個字典中沫屡,然后提供給程序使用饵隙。代碼如下:
//假設(shè)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設(shè)置了一個nil值,證明要從Table最開始的Key進行遍歷勺届。如圖:
值得注意的是驶俊,在獲取Key值時,最好先判斷Key的類型免姿,然后再根據(jù)其對應(yīng)類型調(diào)用相應(yīng)的lua_toXXX
方法饼酿。否則,因為lua_toXXX
系列方法會對元素值進行類型轉(zhuǎn)換胚膊,如整型的Key被lua_tostring
轉(zhuǎn)換為String后再給到lua_next
進行遍歷就會報找不到指定Key的錯誤故俐。
8.7 獲取自定義數(shù)據(jù)
利用lua_topointer
方法來獲取自定義數(shù)據(jù),如:
lua_getglobal(self.state, "ud");
NSObject *obj = (__bridge User *)(lua_topointer(state, 1));
9. 結(jié)語
Lua雖然小巧紊婉,但五臟俱全药版。最主要的是它提供了豐富又強大的C Api接口允許我們進行高度的定制和擴展∮骼纾可利用這些擴展特性開發(fā)適合自己的一套腳本引擎槽片『位海基于上面所講的绅项,鄙人開發(fā)了一個LuaScriptCore的開源橋接框架绘雁,簡化Lua與各種平臺的交互,有興趣的同學(xué)可以了解一下辞嗡。
最后蝙云,希望通過本篇文章能夠讓大家對OC與Lua之間的交互有更近一步的了解氓皱,創(chuàng)造更多的可能性。