原文:http://luanova.org/ioswithlua/
本文討論用Lua建立iOS應用的3種方法。包括用Lua建立完整的應用(Corona)一直到用Lua作為app中的指令碼元素(通過Wax或diy)。在此之前,我們需要問自己兩個問題:
1、為什麼要使用Lua。
2、蘋果允許使用Lua嗎。
這兩個問題是緊密相關的。
如果你在此之前對Lua一無所知,我會簡單介紹一下Lua。如果你熟悉Lua,則可以跳過這部分內容。
關於Lua
Lua是一個高效、輕量級、嵌入式指令碼語言。類似Javascrip、Ruby或Python。有許多和我一樣的使用者,都認為Lua是一種簡潔優雅的語言。
Lua始於1993年巴西裡約熱內盧宗鐸天主教大學的Roberto Ierusalimschy, Waldemar Celes 和Luiz Henrique de Figueiredo。它應用在Mi Casa Verde, Adobe Lightroom, Celestia, lighttpd,LuaTeX, nmap,Wireshark, 思科的Adaptive Security Appliance, pbLua,以及成千上萬的遊戲中(包括 Grim Fandango(神通鬼大),魔獸世界,Tap Tap Revenge(勁樂團)等)。Lua採用MIT協議——這意味著Lua基本無礙於商業和非商業目的。
Lua的主要資料構建機制是表——可變數組和雜湊表的結合。列表1列出了一個Lua Table,假設我們用於描述汽車和它的每加侖行駛裡程數。我們可以用字串類型的鍵來儲存車輛資訊,如license和make。而採用數字下標索引的方式儲存一系列的每加侖裡程數。
列表 1: 一個 Lua Table類型
car_data = { license = 'XVW1942', make = 'Volvo', model = 'XC70',30, 31.3,32.4, 34.0}
print(car_data[1]) -- 30
print(car_data['license']) -- XVW1942
print(car_data.license) -- XVW1942 (also!)
在Lua中,數組下標從1開始,而不是0。'--'表示注釋開始直至行末。讓你想不到的是,Lua既不是物件導向的語言,也不是函數程式設計語言。但是,它也提供了幾種機制允許你定製自己的進階特性。Lua中內包含了各種不同的對象系統如:傳統OO系統和無類化OO系統(比如Oracle的Self語言和Io語言。譯者注* 這兩種都是基於範型的語言)。Lua支援“first class 函數”(譯者注*其實是匿名函數, Lua可以在運行時隨時構造出一個函數,並把它看作一個對象),閉包以及元特性(如元表和元方法)。Lua能很好地滿足函數式編程的需要。
關於Lua 的物件導向編程的介紹,請閱讀《Lua 編程》(這本書很好地介紹了Lua的方方面面)。你也應當閱讀Luawiki上的Lua樣本。
列表2列出了鏈表類的一種實現。變數List實際上是table,用作所有鏈表對象的元表。它實現了一種向後尋找表索引的類似於類的事件處理機制。"List.__index=List"一行允許我們為List對象建立方法。方法是儲存在List元表中的函數。當我們調用list對象的這些函數時,將在List元表中尋找這些函數的定義並運行。
這段代碼顯示了Lua的一系列增強特性:多賦值(以及函數返回多個傳回值),方法調用文法糖(':' 符號的作用,類似於在函數調用中增加了一個self參數,這在許多語言,從Python到OC都能見到)。
列表 2: Linked List 類
List = {}
List.__index = List
function List:new()
local l = { head = {},tail = {}, size = 0 }
l.head.__next,l.tail.__prev = l.tail, l.head
return setmetatable(l,self)
end
function List:first()
if self.size > 0 then
returnself.head.next
else
return nil
end
end
function List:addFirst(elem)
local node = { prev= self.head, value = elem,
next = self.head.next }
node.next.prev =node
self.head.next =node
self.size =self.size + 1
end
mylist = List:new()
mylist:addFirst(12)
print(mylist:first())
在這裡,我忽略了一些重要的和有趣的東西(比如閉包)。但至少,你已經學到了一點Lua的皮毛。在後面我們進入iPhone編碼的時候,會看到更多的Lua代碼。更多關於Lua的介紹,請閱讀這個網站。
iOS支援指令碼嗎。
正如本文開頭列出的兩個問題,尤其是第2個問題:“iPhone允許使用Lua(或其他解釋型語言)嗎。”畢竟,早在蘋果的IDP許可協議中就已經闡明“只有蘋果官方的API和內建解譯器所支援的解釋型代碼能被下載或用於app中”。
事實上,本文的擬寫大綱時,蘋果已經改變了原來禁止開發人員在app使用除OC和Javascript(Javascript能在web app或者本地 app中使用——通過UIWebView)以外的其他語言的條款(circa2010 四月)。最近(2010 九月),蘋果再次改變了這個條款,允許使用指令碼語言。
但仍然有幾個重要的限制。更主要的是,雖然你可以使用Lua(及其它指令碼語言),但你的app不能允許使用者從web上下載外掛程式(用過應用程式購買嗎。),也不能允許使用者編寫指令碼、下載指令碼等。有大量的商店應用在使用Lua這樣的語言(比如勁樂團)。
當然,在app中包含Lua這類語言的兩個最為主要的作用,就是建立外掛程式系統,讓使用者自己能夠編寫指令碼。除此之外還有許多。
如何在iOS開發中使用Lua。
儘管你不能為終端使用者建立一個外掛程式系統,也不能讓使用者自己編寫指令碼,但你仍然能以一種外掛程式的方式開發你的系統。這可以加快原型的開發速度,同時在下個版本中有助於添加新的功能。使用Lua還有另一種好處,它允許你進行“快速原型開發”(我最喜歡抱怨的一句話:不要閉門造車式地編程),緩解甚至不需要記憶體管理,允許更多的團隊成員參與到開發中來(有許多Lua項目根本沒有程式員在編寫代碼),應用程式最佳化更加輕鬆,提供更強勁的持久化機制。
簡而言之,Lua節省了開發時間,降低了開發門檻。生活變得如此輕鬆愉快。假設你已經決定使用Lua,那麼我們該如何做起呢。
Corona
Ansca Mobile公司的Corona允許你完全用Lua來開發iOS應用,以及Android應用。你可以用同樣的原始碼編譯出iOS和Android程式。這正是Lua(實際上是Corona)為何如此迷人的原因:跨平台。
列表3 是一個app的全部原始碼。
列表3: Swirly Text 應用中的main.lua
local w, h = display.stageWidth, display.stageHeight
local dx, dy, dtheta = 5, 5, 5
local background = display.newRect(0, 0, w, h)
background:setFillColor(255, 255, 255)
local message = display.newText('Hello from Corona', w/2, h/2)
message:setTextColor(0, 0, 200)
local function update(event)
local counter_spin =false
message:translate(dx, dy)
message:rotate(dtheta)
if message.x > wor message.x < 0 then
dx= -1 * dx
counter_spin = true
end
if message.y > hor message.y < 0 then
dy = -1 * dy
counter_spin = true
end
if counter_spin then
dtheta = -1 * dtheta
end
end
Runtime:addEventListener('enterFrame', update)
Corona程式可以用任何文字編輯器開發——我用的是Emacs。所有Lua原始碼中用到的資源(圖片、聲音、資料)必需放在同一個目錄,Corona需要main.lua檔案來啟動app。可以在Corona模擬器中測試代碼(支援Intel cpu和Power pc的Mac)。圖1顯示了我的corona IDE: Emacs(包含Lua檔案出口和項目視窗)、Corona終端(可以從診斷中列印調試資訊),以及Corona模擬器。
圖 1: 我的 Corona 'IDE'
從左至右(反時針方向):Corona模擬器、Emacs的兩個視窗(原始碼視窗和項目目錄視窗)、Corona 終端(輸出調試資訊)。
想要在物理硬體上運行程式,使用Corona 模擬器的Openfor Build命令。要以iOS編譯,你應該提供一個Provisioning Profile(開發或部署)——沒錯,你並不需要IDP帳號——這兩個檔案隨同app代碼和資源一同上傳到Ansca公司的伺服器上,然後將編譯結果返回給你。要以Android編譯,你應當有適當的簽署憑證。然後隨著編譯過程把你的代碼傳到Ansca的伺服器上。你不必安裝AndroidSDK。我沒有太深入地研究,但編譯後的.apk檔案和.app檔案已經包含了所有你Lua代碼以某種方式處理過的東西。短暫的查看後表明,那不是標準的編譯後的Lua位元組碼,但應該是類似的格式。
Corona事件系統可以處理觸摸(包括多點觸摸),訪問GPS和加速器,處理動畫以及自訂事件。它還有一個強大的圖形系統,允許你繪製圓、矩形和文本。最近還增加了折線,允許你繪製多邊形。你可以顯示圖片。Corona允許你把這些對象組合在一起然後對他們進行變換。列表4,摘自太陽系模擬器的程式碼片段,展示了組合多個繪圖物件的簡單例子。其他Corona支援的特性還包括聲視頻播放,密碼編譯演算法庫,LuaSocket網路程式庫,SQLite存取庫LuaSQLite等。還能訪問本機群組件包括textfield、alert和activityindicator。你還可以用webview做諸如登入螢幕之類的事情,有一個樣本程式提供了一個庫,可以串連到Facebook。我最近看到有一個遊戲(很貴)使用了Box2D物理引擎、角色和一些OpenFeint的功能(類似熱門排行榜)。
列表 4: 太陽系應用程式碼片段
function new(params)
local color =params.color or planet_colors[random(#planet_colors)]
local radius =params.radius or planetRadius()
local planet =display.newGroup()
planet.theta = 0
local x = params.x -ox
local y = params.y -oy
planet.orbital_radius = sqrt(x*x+y*y)
local body =display.newCircle(x + ox, y + oy, radius, radius)
body:setFillColor(unpack(color))
planet:insert(body,true)
planet.body = body
planet.delta_theta =(40/planet.orbital_radius) * 0.1
return planet
end
把table作為函數參數傳遞,可以使用具名引數並提供預設值。因此會有 local radius = params.radius orplanetRadius() 這樣的寫法。
Corona為你做了許多,但同時也有許多不足。最大的問題是對本地控制項的訪問限制。由於Corona模擬器的限制,它對本地控制項的訪問是糟糕的。在模擬器中,本地alerts和activityindicators用OSX equivalents實現而不是iOS widgets實現的。textfield、textbox以及web popups在模擬器運行時是停用。這在開發時讓人痛苦。
最後,除了ANSCA標準以外,無法訪問O-CAPI。不僅是大量的標準庫,而且第3方庫也無法使用,如Three20或 mobileads 這樣的APIs。當然,隨著Corona Android版本的發布,你可能不想訪問OC API因為它限制你的應用程式跨平台的能力(或者增加了複雜性)。最好是通過Lua的CAPI來擴充,就像是許多跨平台的項目一樣。
我在ANSCA 小組在他們論壇上的討論非常有用。隨著2.0版本(2010.9)的發布,Corona向每位開發人員每年收取249美金。對於遊戲版,收費每開發人員每年349美金。Ansca公司的網站暗示遊戲版的價格只是預覽版的。這意味著當正式版發布時價格將會更高。
DIY
很容易把Lua解譯器放到iOS app中。開啟一個Xcode項目把Lua的源檔案(除lua.c和luac.c命令列程式外)加到項目中。編譯。你就可以使用標準的LuaC API去建立一個解釋起並運行原始碼,就像 iLua所做的。你可以在http://github.com/profburke/ilua下載這個範例程式碼。iLuaShell是一個簡單的view-based application,它提供兩個文字框給使用者——一個給使用者輸入Lua代碼,另一個是不可編輯的,僅僅是顯示Lua代碼計算的結果。
這個工作用evaluate方法完成,如列表5所示。在方法中首先擷取第1個文本域的值,把它交給Lua解譯器解析和執行,然後把Lua輸出結果放到第2個文本域中。
列表 5 evaluate 方法
-(void)evaluate {
int err;
[input resignFirstResponder];
lua_settop(L, 0);
err = luaL_loadstring(L, [input.text
cStringUsingEncoding:NSASCIIStringEncoding]);
if (0 != err) {
output.text = [NSString stringWithCString:lua_tostring(L, -1)
encoding:NSASCIIStringEncoding];
lua_pop(L, 1);
return;
}
err = lua_pcall(L, 0, LUA_MULTRET, 0);
if (0 != err) {
output.text = [NSString stringWithCString:lua_tostring(L, -1)
encoding:NSASCIIStringEncoding];
lua_pop(L, 1);
return;
}
int nresults =lua_gettop(L);
if (0 == nresults) {
output.text = @"<no results>";