LuaView SDK第二版设计插件化理解(二)
第一篇提到了MILExporter类,及它的相关的Api,下面看一下它里面的具体实现。
- (void)reg:(lua_State *)L clazz:(const char *)clazzName constructor:(const char *)constructorName cfunc:(lua_CFunction)cfunc name:(const char *)luaName 注册key到Lua虚拟机的全局表。 传入了className,constructorName。以className为key,具体的闭包为value进行写入。具体代码如下。
+ (void)reg:(lua_State *)L clazz:(const char *)clazzName constructor:(const char *)constructorName cfunc:(lua_CFunction)cfunc name:(const char *)luaName
{
NSAssert(mm_CharPointIsNotNULL(clazzName), @"The class must not be nil!");
NSAssert(mm_CharPointIsNotNULL(luaName), @"The lua name of class must not be nil!");
lua_checkstack(L, 12);
// 把native类名压栈
lua_pushstring(L, clazzName);
// 是否为属性压栈。这个暂时没用到
lua_pushboolean(L, NO); // 不是属性
// 把初始化方法名称压栈。如果没有的话则走默认的
lua_pushstring(L, mm_CharPointIsNotNULL(constructorName) ? constructorName : "init");
// 设置closure的upValue 出栈相应的元素,闭包压栈
lua_pushcclosure(L, cfunc, 3);
// 设置闭包到全局表
lua_setglobal(L, luaName);
}
-
这个文件的一个核心函数。void mm_lua_openlib (lua_State *L, const char *libname, const struct mm_lua_objc_method *l, int nup) { 注册实例方法到元表和注册类方法到类对应的表。
void mm_lua_openlib (lua_State *L, const char *libname, const struct mm_lua_objc_method *l, int nup) { // 如果libname存在。则相当于注册类方法。 if (libname) { int size = mm_libsize(l); /* check whether lib already exists */ luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); lua_getfield(L, -1, libname); /* get _LOADED[libname] */ if (!lua_istable(L, -1)) { /* not found? */ lua_pop(L, 1); /* remove previous result */ /* try global variable (and create one if it does not exist) */ if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL) luaL_error(L, "name conflict for module " LUA_QS, libname); lua_pushvalue(L, -1); lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */ } lua_remove(L, -2); /* remove _LOADED table */ lua_insert(L, -(nup+1)); /* move library table to below upvalues */ } // 上面的过程相当于在LUA_GLOBALSINDEX 表中获取libname对应的表 for (; l->l_mn; l++) { NSCAssert(mm_CharPointIsNotNULL(l->clz), @"The class name must not be nil!"); NSCAssert(l->func!=NULL, @"The C function must not be NULL!"); int extraCount = 0; // 类名压栈 lua_pushstring(L, l->clz); // class // bool 属性压栈 lua_pushboolean(L, l->isProperty); if (l->isProperty) { NSCAssert(mm_CharPointIsNotNULL(l->setter_n), @"The method name must not be nil!"); // 为属性则setter_n和getter_n 字符串压栈 lua_pushstring(L, l->setter_n); // setter NSCAssert(mm_CharPointIsNotNULL(l->getter_n), @"The method name must not be nil!"); lua_pushstring(L, l->getter_n); // getter extraCount = 4; } else { NSCAssert(mm_CharPointIsNotNULL(l->mn), @"The method name must not be nil!"); //否则则 selector名称压栈 lua_pushstring(L, l->mn); // selector extraCount = 3; } int i; for (i=0; i
func, (nup+extraCount)); // 找到 -(nup+2) 这个位置上面的表,设置l->l_mn为key,栈顶元素为value 写入表。 lua_setfield(L, -(nup+2), l->l_mn); } // 栈中移除相应数量的元素 lua_pop(L, nup); /* remove upvalues */ } 下面来看下非常核心的Lua与native的通讯部分,刘旭对原来的通讯方式进行完全的改变。这是LuaViewSDK优化最核心的部分。
上面一直提到我们自己构建了结构体对象。我们映射到Lua的只是这个结构体,映射到lua的全部类方法和对象方法都可以通过这个结构体的MMLua_Method 表示。Lua 去掉用userdata方法,原本通过各个单独的方法调用,现在改为全部都路由到一个C的Api里面,然后在这个Api里面进行消息的分发。下面看下具体的结构体。
struct mm_lua_objc_method ;
typedef struct mm_lua_objc_method *MMLua_Method;
struct mm_lua_objc_class;
typedef struct mm_lua_objc_class *MMLua_Class;
typedef struct mm_lua_objc_method{
const char *l_mn; /* Object-C method name in lua*/
const char *mn; /* Object-C method name */
const char *clz; /* Object-C class name */
BOOL isProperty; /* It's YES if property method*/
const char *setter_n; /* Object-C getter method name*/
const char *getter_n; /* Object-C setter method name */
lua_CFunction func; // 元表对应的func。注册进去的都是func。
}mm_lua_objc_method;
typedef struct mm_lua_objc_class{
const char *pkg; // package name
const char *clz; // 类名
const char *l_clz; ///* Object-C class name in lua */
const char *l_type; /* its type of Object-C class in lua */
BOOL isRoot; /* is root function,it should be YES if no base class. */
const char *supreClz; /* base Object-C class */
BOOL hasConstructor; /* it should be NO if static class. */
MMLua_Method methods; /* Object-C method */
MMLua_Method clz_methods; /* Object-C class method */
// 初始化函数的描述
struct mm_lua_objc_method constructor; /* Object-C constructor method */
}mm_lua_objc_class;
下面以 LUA_EXPORT_VIEW_PROPERTY(image, "lua_setImage:", "lua_image", MMGraphicButton) 为例。看下具体这个宏定义为我们做了什么。连续点击进去可以发现这个宏最终会我们生成mm_lua_objc_method 这个结构体的一个实例。{image,NULL,MMGraphicButton,YES,"lua_setImage","lua_image",mm_lua_router_method};
- 此时我们再去查看发现所有的Lua去调用对象方法实际都会路由到我们的mm_lua_router_method中,然后通过这个方法再去生成相应的Invocation 进行invoke调用。我们来具体看下mm_lua_router_method具体做了什么。
int mm_lua_router_method (lua_State *L) { NSCAssert(L, @"The lua state must not be null!"); // class 首先获取Class Class clazz = mm_lua_resolveClass(L); // 同理在closure的上值中获取是否为Property BOOL isProperty = mm_lua_resolveIsProperty(L); // 获取SEL。 如果非属性则直接在上值中获取Sel名称转为SEL SEL selector = isProperty ? mm_lua_resolveGetterOrSetter(L) : mm_lua_resolveSelector(L); // 方法的调用。 return mm_lua_call_objc_method(L, selector, clazz); } Class mm_lua_resolveClass (lua_State *L) { NSCAssert(L, @"The lua state must not be null!"); NSCAssert(lua_isstring(L, lua_upvalueindex(1)), @"The first upvalue must be a string of class name!"); // 通过伪索引 去获取Closure中的上值。 NSString *clazzString = [NSString stringWithUTF8String:lua_tostring(L, lua_upvalueindex(1))]; NSCAssert(clazzString && clazzString.length > 0, @"The class name must not be nil!"); // 将上值存储的string转成Class。上面的openLib方法涉及到了上值的存储。会存储到生成的Closure的upValue数组中。具体的闭包调用的时候在Lua虚拟机的ci的func拿到func,此时为TValue对象,通过TValue找到Closure。 return NSClassFromString(clazzString); } // 在上值中获取是否为Property. BOOL mm_lua_resolveIsProperty (lua_State *L) { NSCAssert(L, @"The lua state must not be null!"); NSCAssert(lua_isboolean(L, lua_upvalueindex(2)), @"The first upvalue must be a string of selector name!"); return lua_toboolean(L, lua_upvalueindex(2)); } // 同理通过Lua栈中参数的个数获取是get还是set方法。然后通过上值将String转成Selector SEL mm_lua_resolveGetterOrSetter (lua_State *L) { NSCAssert(L, @"The lua state must not be null!"); int selIdx = [MILExporter isLuaCallGetter:L] ? 4 : 3; // setter's index is 3, getter's index is 4 return mm_lua_resolveSelectorAtIndex(L, selIdx); }
- 上面提到了mm_lua_call_objc_method 下面来看下到底做了什么。
int mm_lua_call_objc_method (lua_State *L, SEL selector, Class
clazz) { NSCAssert(L, @"The lua state must not be null!"); // 在栈底获取Userdata对象并进行类型判断。返回userdata->object对象 id targetObj = [MILExporter targetOnLuaCall:L class:clazz]; // 重置LuaCore mm_lua_resetLuaCore(L, clazz); // 进行真实的方法的调用。 return mm_lua_call_objc(L, targetObj, selector, NO); } /* 1. 通过SEL获取方法签名 2. 通过方法签名生成NSInvocation 3. 设置Invocation的SEL,target,参数 4. 执行invocation的invoke的操作 5. 通过invocation的returnType 确定返回值的个数 **/ int mm_lua_call_objc (lua_State *L, id target, SEL selector, BOOL isclass) { NSCAssert(L, @"The lua state must not be null!"); NSCAssert(target, @"The target must not be nil!"); // 获取方法前面通过SEL NSMethodSignature *sig = [target methodSignatureForSelector:selector]; // 确定方法前面中参数的个数 NSUInteger argsCount = [sig numberOfArguments] - 2; // Lua堆栈中个数 int l_argsCount = [MILExporter numOfArgsOnLuaCallIn:L]; // NSCAssert(argsCount==l_argsCount, @"The number of parameters does not match!"); // 通过方法签名生成NSInvocation 对象 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; // 设置target [invocation setTarget:target]; // 设置SEL [invocation setSelector:selector]; // 后续是参数的设置 NSMutableArray *retainArray = [NSMutableArray arrayWithCapacity:argsCount]; // retain invocation中的target和参数 [invocation retainArguments]; for (int i = 2; i <= argsCount + 1; i++) { int argIdx = i; int stackIdx = i; MMTypeConvertorErr success = mm_setInvocationArgByLua(invocation, argIdx, L, stackIdx, retainArray, !isclass); switch (success) { case MMTypeConvertorUndef: lua_settop(L, 0); lua_pushstring(L, "Undefined parameter type"); return 1; case MMTypeConvertorTypeErr: lua_settop(L, 0); lua_pushstring(L, "An error occurred about the parameter type"); return 1; default: break; } } // invocation的调用。 [invocation invoke]; return mm_pushInvocationReturnToLua(invocation, L); } // 下面具体看一下setArgument: atIndex:的过程 int mm_setInvocationArgByLua(NSInvocation *invocation, int index, lua_State *L, int stackID, NSMutableArray *retainArray, BOOL hasTarger4Block) { // 通过方法前面获取指定位置参数的描述const char* const char* type = [invocation.methodSignature getArgumentTypeAtIndex:index]; if (type){ switch (mm_typeOfObjc(type)) { case MM_OBJCType_BOOL: { // Lua 栈相应位置拿到值 BOOL value = lua_toboolean(L, stackID); // 设置到invocation相应的位置 [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_class: { if (lua_isstring(L, stackID)) { Class clazz = NSClassFromString(lv_paramString(L, stackID)); [invocation setArgument:&clazz atIndex:index]; } return 0; } case MM_OBJCType_block: { if (lua_isfunction(L, stackID)) { MILBlock *block = nil; if (hasTarger4Block) { block = [[MILBlock alloc] initWithTarget:invocation.target luaCore:LV_LUASTATE_VIEW(L) index:stackID]; } else { block = [[MILBlock alloc] initWithLuaCore:LV_LUASTATE_VIEW(L) index:stackID]; } MILCallback callback = [^(id result, BOOL keepAlive){ [block callWithParam:result]; } copy]; [retainArray addObject:callback]; // 设置callback到指定位置 [invocation setArgument:&callback atIndex:index]; } return 0; } case MM_OBJCType_SEL: { if (lua_isstring(L, stackID)) { SEL selector = NSSelectorFromString(lv_paramString(L, stackID)); [invocation setArgument:&selector atIndex:index]; } return 0; } case MM_OBJCType_id: { id nativeObject = nil; if (hasTarger4Block) { // lua对象转native对象 nativeObject = mm_lua_tonativeobj(L, stackID, invocation.target); } else { nativeObject = mm_lua_tonativeobj(L, stackID, nil); } [invocation setArgument:&nativeObject atIndex:index]; return 0; } case MM_OBJCType_char: { char value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_uchar: { unsigned char value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_short: { short value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_ushort: { unsigned short value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_int: { int value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_uint: { unsigned int value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_long: { long value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_ulong: { unsigned long value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_llong: { long long value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_ullong: { unsigned long long value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_float: { float value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_double: { double value = lua_tonumber(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_char_ptr: { char *value = lua_touserdata(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_void_ptr: { void *value = lua_touserdata(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_const_char_ptr: { const char *value = lua_touserdata(L, stackID); [invocation setArgument:&value atIndex:index]; return 0; } case MM_OBJCType_rect: { CGRect orig_rect = mm_lua_tocgrect(L, stackID); [invocation setArgument:&orig_rect atIndex:index]; return 0; } case MM_OBJCType_size: { CGSize orig_size = mm_lua_tocgsize(L, stackID); [invocation setArgument:&orig_size atIndex:index]; return 0; } case MM_OBJCType_point: { CGPoint orig_point = mm_lua_tocgpoint(L, stackID); [invocation setArgument:&orig_point atIndex:index]; return 0; } default: { NSInteger value = 0; NSCAssert(value, @"Undefined parameter type!"); [invocation setArgument: &value atIndex:index]; LVError(@"Undefined parameter type!"); return 1; // 参数类型不支持 } } } NSCAssert(NO, @"An error occurred about the parameter type!"); return 2; // 获取参数类型失败 } /** 获取oc数据类型 mm_typeOfObjc */ MM_Objc_Type mm_typeOfObjc(const char *type) { switch (type[0]) { // id类型 case _C_ID: //#define _C_ID '@' // 则返回Block if (type[1] == _C_UNDEF) { '?' return MM_OBJCType_block; } return MM_OBJCType_id; case _C_CLASS: //#define _C_CLASS '#' return MM_OBJCType_class; case _C_SEL: //#define _C_SEL ':' return MM_OBJCType_SEL; case _C_CHR: //#define _C_CHR 'c' return MM_OBJCType_char; case _C_UCHR: //#define _C_UCHR 'C' return MM_OBJCType_uchar; case _C_SHT: //#define _C_SHT 's' return MM_OBJCType_short; case _C_USHT: //#define _C_USHT 'S' return MM_OBJCType_ushort; case _C_INT: //#define _C_INT 'i' return MM_OBJCType_int; case _C_UINT: //#define _C_UINT 'I' return MM_OBJCType_uint; case _C_LNG: //#define _C_LNG 'l' return MM_OBJCType_long; case _C_ULNG: //#define _C_ULNG 'L' return MM_OBJCType_ulong; case _C_LNG_LNG: //#define _C_LNG_LNG 'q' return MM_OBJCType_llong; case _C_ULNG_LNG: //#define _C_ULNG_LNG 'Q' return MM_OBJCType_ullong; case _C_FLT: //#define _C_FLT 'f' return MM_OBJCType_float; case _C_DBL: //#define _C_DBL 'd' return MM_OBJCType_double; case _C_BOOL: //#define _C_BOOL 'B' return MM_OBJCType_BOOL; case _C_VOID: //#define _C_VOID 'v' return MM_OBJCType_void; case _C_CHARPTR: //#define _C_CHARPTR '*' return MM_OBJCType_char_ptr; case _C_STRUCT_B: { //#define _C_STRUCT_B '{' 结构体 // #define mm_strcmp(a, b) (strcmp((a), (b)) == 0) if (mm_strcmp(type, @encode(CGRect))) { return MM_OBJCType_rect; } else if (mm_strcmp(type, @encode(CGSize))) { return MM_OBJCType_size; } else if (mm_strcmp(type, @encode(CGPoint))) { return MM_OBJCType_point; } return MM_OBJCType_struct; } case _C_PTR: { //#define _C_PTR '^' if (type[1] == _C_ID) { ? return MM_OBJCType_id_ptr; } else if (type[1] == _C_STRUCT_B) { return MM_OBJCType_struct_ptr; } else if (mm_strcmp(type, @encode(void *))) { return MM_OBJCType_void_ptr; } //TODO: 待支持其他类型 return MM_OBJCType_ndef; } case _C_CONST: { //#define _C_CONST 'r' 常量 if (mm_strcmp(type, @encode(const char *))) { return MM_OBJCType_const_char_ptr; } //TODO: 待支持其他类型 return MM_OBJCType_ndef; } default: //#define _C_UNDEF '?' return MM_OBJCType_ndef; } }
上面提到的各种type_VOID等 实际对应了一张表在runtime.h中。对应了所有的参数的类型对应的字符串
#define _C_ID '@'
#define _C_CLASS '#'
#define _C_SEL ':'
#define _C_CHR 'c'
#define _C_UCHR 'C'
#define _C_SHT 's'
#define _C_USHT 'S'
#define _C_INT 'i'
#define _C_UINT 'I'
#define _C_LNG 'l'
#define _C_ULNG 'L'
#define _C_LNG_LNG 'q'
#define _C_ULNG_LNG 'Q'
#define _C_FLT 'f'
#define _C_DBL 'd'
#define _C_BFLD 'b'
#define _C_BOOL 'B'
#define _C_VOID 'v'
#define _C_UNDEF '?'
#define _C_PTR '^'
#define _C_CHARPTR '*'
#define _C_ATOM '%'
#define _C_ARY_B '['
#define _C_ARY_E ']'
#define _C_UNION_B '('
#define _C_UNION_E ')'
#define _C_STRUCT_B '{'
#define _C_STRUCT_E '}'
#define _C_VECTOR '!'
#define _C_CONST 'r'
下面看下Lua端调用以上方法返回部分。也就是调用方法后,相应的native对象需要转为相应的lua支持的对象压栈。
int mm_pushInvocationReturnToLua(NSInvocation* invocation, lua_State* L) {
// 通过方法签名拿到返回参数的字符串描述
const char *type = [invocation.methodSignature methodReturnType];
if (type){
// 上面已经说了。具体看上面runtime的一个表去对应
switch (mm_typeOfObjc(type)) {
case MM_OBJCType_void:
return 0;
case MM_OBJCType_BOOL: {
BOOL result = 0;
// 传入参数的地址。方法内部会根据传入的指针会修改相应内存的值
[invocation getReturnValue: &result];
lua_pushboolean(L, result);
return 1;
}
case MM_OBJCType_class: {
Class clazz = nil;
[invocation getReturnValue:&clazz];
if (clazz) {
lua_pushstring(L, NSStringFromClass(clazz).UTF8String);
} else {
lua_pushnil(L);
}
return 1;
}
case MM_OBJCType_SEL: {
SEL sel = nil;
[invocation getReturnValue:&sel];
if (sel) {
lua_pushstring(L, NSStringFromSelector(sel).UTF8String);
} else {
lua_pushnil(L);
}
return 1;
}
case MM_OBJCType_id: {
void *result = nil;
[invocation getReturnValue:&result];
mm_lua_pushnativeobj(L,(__bridge id)result);
return 1;
}
case MM_OBJCType_char: {
char result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_uchar: {
unsigned char result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_short: {
short result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_ushort: {
unsigned short result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_int: {
int result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_uint: {
unsigned int result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_long: {
long result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_ulong: {
unsigned long result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_llong: {
long long result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_ullong: {
unsigned long long result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_float: {
float result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_double: {
double result = 0;
[invocation getReturnValue: &result];
lua_pushnumber(L, result);
return 1;
}
case MM_OBJCType_char_ptr: {
char *result = 0;
[invocation getReturnValue: &result];
lua_pushlightuserdata(L, result);
return 1;
}
case MM_OBJCType_void_ptr: {
void *result = 0;
[invocation getReturnValue: &result];
lua_pushlightuserdata(L, result);
return 1;
}
case MM_OBJCType_rect:{
CGRect rect = CGRectZero;
[invocation getReturnValue:&rect];
mm_lua_pushcgrect(L, rect);
return 1;
}
case MM_OBJCType_size:{
CGSize size = CGSizeZero;
[invocation getReturnValue:&size];
mm_lua_pushcgsize(L, size);
return 1;
}
case MM_OBJCType_point:{
CGPoint point = CGPointZero;
[invocation getReturnValue:&point];
mm_lua_pushcgpoint(L, point);
return 1;
}
default: {
NSInteger value = 0;
NSCAssert(value, @"Undefined parameter type!");
[invocation setArgument: &value atIndex:index];
LVError(@"Undefined parameter type!");
return 0; // 参数类型不支持
}
}
}
NSCAssert(NO, @"An error occurred about the parameter type!");
return 0;
}
上面对应每一种返回的参数类型 做了switch。在invocation中获取相应的参数。然后压入lua栈中。普通数据类型,转成Lua可以接收的类型压栈,Lua不能直接接收的id类型。并没有继续采用SDK内部的包装一个LVBox的方式。而是进行了自己的创新,把每一种具体的类型都展开。当然userdata的话转为相应的userdata并压栈。具体代码如下。
void mm_lua_pushnativeobj (lua_State* L, id value) {
lua_checkstack(L, 4);
if( [value isKindOfClass:[NSString class]] ) {
NSString* s = value;
lua_pushstring(L, s.UTF8String);
} else if( [value isKindOfClass:[NSDictionary class]] ) {
NSDictionary* dictionary = value;
lua_newtable(L);
for (NSString *key in dictionary) {
NSString* value = dictionary[key];
lua_checkstack(L, 4);
lua_pushstring(L, key.UTF8String);
mm_lua_pushnativeobj(L,value);
lua_settable(L, -3);
}
} else if( [value isKindOfClass:[NSArray class]] ) {
NSArray* array = value;
lua_newtable(L);
for (int i=0; i)value);
} else {
NSCAssert(NO, @"An error occurred about the parameter type!");
}
}
返回值为Lua端在栈中获取相应参数的个数