經常有看到一些游戲可以不通過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
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。