经常有看到一些游戏可以不通过appstore而修改一些东西,比如增加功能。
这个其实就是通过下载脚本来实现的。常见的脚本就是js和lua吧。
个人对lua比较熟悉。
lua这货还是挺牛的,解释器非常小,速度也非常快,和C语言函数的交互也很容易。
集成lua到xcode工程
很容易,
1. 到lua官网,下载源代码,http://www.lua.org/download.html
2. 在要集成lua的工程里面,添加一个target,选择静态库。
3. 把lua源代码(.h, .c)拉到新建的静态库里面,编译。
4. 在目标工程里面,link刚编译出来的.a文件。如:
这样环境就算弄好了。
使用lua
先include几个lua的头文件。
//extern "C"
//{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
//}
如果是c++环境,则需要把extern "C"打开。
准备Lua脚本文件
这里,就简单的创建一个资源。实际上这个脚步可以从服务器下载,保存在本地机器。
创建本地资源文件很简单,我这里就直接创建一个空的文件,命名为my.lua。
然后写入脚本:
这个脚本非常的简单,就是定义一个全局变量myname和一个函数lefthandcall。
在函数内部调用一个lhc的函数,返回。
调用lua函数
直接给出代码,看注释:
- (IBAction)testLua:(id)sender {
l = luaL_newstate(); // 新建一个lua状态
luaL_openlibs(l); // 加载lua库
NSString *scriptpath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"my.lua"]; // 读取资源文件里面的lua文件路径
// NSString* content = [NSString stringWithContentsOfFile:scriptpath encoding:NSUTF8StringEncoding error: nil];
int iError = luaL_loadfile(l, scriptpath.UTF8String); // 加载lua文件
iError = lua_pcall(l, 0, 0, 0); // 先执行一下,可以获取lua脚本的一些变量
int iTop = lua_gettop(l); // 获取栈顶元素的索引,这里应该是0,也就是栈还是空的。
lua_getglobal(l, "myname"); // 获取lua里面的一个名字为myname的全局变量,这个全局变量的值会压栈
iTop = lua_gettop(l); // 所以这里应该是1, 栈顶索引时1,也就是只有一个元素。
const char* p = lua_tostring(l, -1); // 可以获取lua脚本里面的myname变量的值。
printf("global variable value: %s", p);
lua_pushcfunction(l, myTest); // 把一个c语言函数压栈。
iTop = lua_gettop(l); // 现在应该是2
lua_setglobal(l, "lhc"); // 把栈顶的myTest函数设置成lua环境里面的全局变量,名字为lhc,lua_setglobal函数有出栈功能。
iTop = lua_gettop(l); // 这个时候,应该是1,这个元素其实就是myname变量的值,可以再次尝试获取。
p = lua_tostring(l, -1);
lua_pop(l, 1); // 从栈顶移除一个元素。
iTop = lua_gettop(l); // 应该是0
lua_getglobal(l, "lefthandcall"); // 获取lua脚本里面的名字为lefthandcall的函数,压栈。
iTop = lua_gettop(l); // 应该是1
lua_pushnumber(l, 15); // 压入参数
lua_pushnumber(l, 20); // 压入参数
iTop = lua_gettop(l); // 栈顶索引是3,一个lua脚本的函数,2个参数。
iError = lua_pcall(l, 2, 1, 0 ); // 调用lua函数,会把栈里面的lua函数和两个参数移除,然后把返回值压栈。
iTop = lua_gettop(l); // 3 - 3 + 1 = 1
printf("ret: %s", lua_tostring(l, -1)); // 打印返回值。
lua_pop(l, iTop); // 清空栈。
iTop = lua_gettop(l); // 0
lua_close(l); // 关闭lua状态。
}
上面的代码主要做了这么几个事情:
1. 新建一个lua状态
2. 获取一个lua脚本里的全局变量。
3. 把一个C语言函数myTest压栈,并且设置成lua环境里面的一个全局函数。
4. 获取lua脚本的函数,并且压入相应的参数
5. 调用lua脚本函数。
mytest函数代码如下:
int myTest(lua_State* L) // 这个函数会被脚本执行。
{
// 先检测传入的2个参数是否为数值型
if (!lua_isnumber(L, 1)){
return lua_error(L);
}
if (!lua_isnumber(L, 2)){
return lua_error(L);
}
double a = lua_tonumber(L, 1);
double b = lua_tonumber(L, 2);
a>b?lua_pushnumber(L, a):lua_pushnumber(L, b); // 把2个参数里面大的值压栈,也就是返回值。
root.myLabel.hidden = NO; // 显示一个原来是隐藏的label。
return 1;
}
基本上,整个过程就是:
1. iOS的按钮响应函数调用lua脚本函数lefthandcall
2. lua脚本函数lefthandcall调用lhc函数
3. lhc是lua环境里的一个全局函数,由#1设置。lhc的本身代码是C语言。也就是上面的myTest函数。
运行一下,就会得到2个结果:
1. myTest函数的返回值,也就是2个参入参数的大的值
2. myTest里面,把一个label给显示出来了。见下图,点击TestLua按钮上,会显示上面的label “Shown by Lua”
总结
在ios开发里面使用lua还是挺简单的,也很好用。上面的例子,我们显示了一个label,实际上可以做更多其他的事情。
比如,扩展一些简单的功能。
有了lua这种动态执行脚本,可以给我们的程序增加一些灵活性。有些时候,做一些功能的改动还是挺好的,可以省去再次审核,但是有时像appstore可能会因为集成了这类功能,而拒绝审核。但是不管怎么样,这总是给了我们一个比较好的小范围调整程序的方法。
当然,如果改动很大,那最好的办法还是发布一个新的app版本,不然lua会整的非常复杂。
附:
xcode 6, iOS sdk 8, lua 5.3
//
// ViewController.m
// TestLua
//
// Created by Kevin on 15/3/3.
// Copyright (c) 2015年 Kevin. All rights reserved.
//
#import "ViewController.h"
//extern "C"
//{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
//}
lua_State *l;
ViewController* root;
int myTest(lua_State* L) // 这个函数会被脚本执行。
{
// 先检测传入的2个参数是否为数值型
if (!lua_isnumber(L, 1)){
return lua_error(L);
}
if (!lua_isnumber(L, 2)){
return lua_error(L);
}
double a = lua_tonumber(L, 1);
double b = lua_tonumber(L, 2);
a>b?lua_pushnumber(L, a):lua_pushnumber(L, b); // 把2个参数里面大的值压栈,也就是返回值。
root.myLabel.hidden = NO; // 显示一个原来是隐藏的label。
return 1;
}
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
root = self;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)testLua:(id)sender {
l = luaL_newstate(); // 新建一个lua状态
luaL_openlibs(l); // 加载lua库
NSString *scriptpath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"my.lua"]; // 读取资源文件里面的lua文件路径
// NSString* content = [NSString stringWithContentsOfFile:scriptpath encoding:NSUTF8StringEncoding error: nil];
int iError = luaL_loadfile(l, scriptpath.UTF8String); // 加载lua文件
iError = lua_pcall(l, 0, 0, 0); // 先执行一下,可以获取lua脚本的一些变量
int iTop = lua_gettop(l); // 获取栈顶元素的索引,这里应该是0,也就是栈还是空的。
lua_getglobal(l, "myname"); // 获取lua里面的一个名字为myname的全局变量,这个全局变量的值会压栈
iTop = lua_gettop(l); // 所以这里应该是1, 栈顶索引时1,也就是只有一个元素。
const char* p = lua_tostring(l, -1); // 可以获取lua脚本里面的myname变量的值。
printf("global variable value: %s", p);
lua_pushcfunction(l, myTest); // 把一个c语言函数压栈。
iTop = lua_gettop(l); // 现在应该是2
lua_setglobal(l, "lhc"); // 把栈顶的myTest函数设置成lua环境里面的全局变量,名字为lhc,lua_setglobal函数有出栈功能。
iTop = lua_gettop(l); // 这个时候,应该是1,这个元素其实就是myname变量的值,可以再次尝试获取。
p = lua_tostring(l, -1);
lua_pop(l, 1); // 从栈顶移除一个元素。
iTop = lua_gettop(l); // 应该是0
lua_getglobal(l, "lefthandcall"); // 获取lua脚本里面的名字为lefthandcall的函数,压栈。
iTop = lua_gettop(l); // 应该是1
lua_pushnumber(l, 15); // 压入参数
lua_pushnumber(l, 20); // 压入参数
iTop = lua_gettop(l); // 栈顶索引是3,一个lua脚本的函数,2个参数。
iError = lua_pcall(l, 2, 1, 0 ); // 调用lua函数,会把栈里面的lua函数和两个参数移除,然后把返回值压栈。
iTop = lua_gettop(l); // 3 - 3 + 1 = 1
printf("ret: %s", lua_tostring(l, -1)); // 打印返回值。
lua_pop(l, iTop); // 清空栈。
iTop = lua_gettop(l); // 0
lua_close(l); // 关闭lua状态。
}
@end