开发”愤怒的小鸟”的Lua语言:Wax框架详解

2021年1月9日   |   by tgcode

  2011年6月的编程语言排行榜Lua语言一军突起,一举进入前十名。这与一年前苹果决定在iOS系统上使用Lua语言密不可分。但是,你了解如何用Lua语言在iOS上开发应用吗?这里将向各位介绍Lua语言的iOS应用开发框架Wax,其中在iOS平台上无比火爆的《愤怒的小鸟》就是由Lua语言用Wax开发的。全文共分两部分,第一部分将带您深入探讨Wax具有的一些好处,同时演示把Lua与Xcode 4和iOS软件开发工具包(SDK)集成起来必不可少的实际步骤。第二部分将逐步介绍如何用Wax构建一个简单的应用程序,显示Twitter上的当前趋势话题列表,可以用按钮来更新内容。

%title插图%num

  Wax是什么?

  Wax for iPhone这种框架在开发时,旨在把Lua脚本语言和原生Objec++tive-C应用编程接口(API)结合起来。这意味着,你可以从Lua里面,使用任何和全部的Objective-C类及框架。

  从技术上来讲,Wax结合了Objective-C类和原生C代码。Lua语言嵌入了C语言,然后Objective-C类并入到其中。

  为什么使用Wax?

  Wax是免费的、开源。与其他一些基于Lua的移动开发解决方案不同,Wax是个开源框架,只需要你花一点点时间就可以上手,不需要花钱。不喜欢Wax的工作方式,或者发现实施方面的缺陷?源代码可免费获取,你总是可以改动源代码,以满足自己的需要。

  可以利用原生API。这意味着,为教Objective-C而编写的教程很容易由Lua for Wax来改动和编写。这还意味着,你的应用程序在外观感觉上总是如同原生应用程序,不过又得到了用Lua这种高效脚本语言编写代码可以节省时间的好处。

  可以使用Xcode。这意味着,模拟器和设备部署都轻而易举,不会轻易与未来的iOS版本决裂。

  可以利用所有现有的Objective-C库。如果你有一个Objective-C类是以前编写的,不需要改动,就可以将它用在Lua中只要把它放入到Xcode。Three20之类的库也是一样。只要按照正常指令来添加库,就可以使用Lua代码访问它们。

  可以利用Wax Lua模块。Wax有几个内置的Lua模块,使得异步HTTP请求和JavaScript对象标注(JSON)创建/解析极其容易而快速(因为模块是用C编写的)。

  没必要管理内存。不再需要操心内存分配之类的事务。Wax为你处理这一切。

  Lua类型自动转换成对应的Objective-C类型,反之亦然。 这意味着,如果你调用了需要NSString和NSInteger的某个方法,但传送了Lua字符串和Lua整数,Wax会为你搞定转换工作。这种转换功能强大,甚至可以处理复杂的Objective-C特性,比如选择器。

  你可以利用所有上述特性。不需要精挑细选。你获得所有特性!

  OK,实在太棒了!我该如何安装Wax?

  首先你需要Xcode和iPhone SDK。要是你还没有这些东西,赶紧弄一份!

  用Xcode创建项目

  我们先创建一个新的基于Windows的项目,名为WaxApplication。别忘了把设备设置成iPhone:

%title插图%num

%title插图%num

  通过Finder浏览到你保存该项目的文件夹。创建三个新的文件夹:wax、scripts和Classes。你的文件夹看起来应该像这样:

%title插图%num

  设置Wax(第一部分,处理文件)

  首先,下载源代码的压缩包。Wax放在GitHub上(https://github.com/probablycorey/wax),那样下载源代码就很容易。从这里下载压缩包。

  现在,解压缩刚下载的文件。浏览到刚解压缩的文件夹。它会有probablycorey-wax-124ca46之类的名称。

  你的屏幕现在看起来应该像这样:

%title插图%num

  现在,执行下列操作:

  ◆ 拷贝lib和bin文件夹,把它们粘贴到位于WaxApplication项目文件夹里面的wax文件夹。

  ◆ 将xcode-template/Classes/ProtocolLoader.h拷贝到WaxApplication项目文件夹。

  ◆ 拷贝xcode-template/scripts/文件夹,将它放到WaxApplication项目文件夹里面。

  ◆ 打开WaxApplication项目文件夹里面的wax/lib/extensions/文件夹。删除SQLite和xml文件夹,下图所示:

%title插图%num

  你的屏幕应该看起来像这样:

%title插图%num

  设置Wax(第二部分,配置项目)

  现在用Finder选择Classes、scripts和wax文件夹,把它们拖入到Xcode项目中。把它们放到显示Wax Application和1 target, iOS SDK X.X的那个条下方。不要勾选显示Copy items into destination group’s folder(if needed)的复选框。点击完成。

  现在点击显示Wax Application和1 target, iOS SDK 4.3的那个条。接着采取下列步骤:

  ◆ 在右边窗格中,寻找Targets标题,点击WaxApplication。点击Build Phases(构建阶段)选项卡。点击Copy Bundle Resources(复制捆绑资源),清除所有lua文件。

  ◆ 在右下角,先点击Add Build Phase(添加构建阶段),再点击Add Run Script(添加运行脚本)。

  ◆ 将Shell设成/bin/zsh

  ◆ 将Shell下面的文本区域设成$PROJECT_DIR/wax/lib/build-scripts/cotgcodepy-scripts.sh。

  你的屏幕现在看起来像这样:

%title插图%num

  改动main.m

  在左边窗格中,打开名为WaxApplication的文件夹。接下来,打开Supporting Files文件夹。接着,打开main.m,把文件的内容换成如下:

//这是发生奇迹的地方!
// Wax并不使用nib文件来装入主视图,一切在AppDelegate.lua文件里面完成
#import UIKit/UIKit.h>
#import wax.h
#import wax_http.h
#import wax_json.h
#import wax_filesystem.h
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool =[[NSAutoreleasePool alloc] init];

wax_start(“AppDelegate.lua”, luaopen_wax_http, luaopen_wax_json, luaopen_wax_filesystem, nil);

int retVal = UIApplicationMain(argc, argv, nil, @”AppDelegate”);
[pool release];
return retVal;
}

  别忘了保存文件!

  删除不必要的文件

  删除MainWindow.xib、WaxApplicationAppDelegate.h和WaxApplicationAppDelegate.m三个文件。打开WaxApplication/Supporting Files/WaxPallication-Info.plist,然后删除键是Main nib file base name的那一行。

  测试安装的Wax

  按↵(命令+回车键),或者按左上角的Run,就可以在模拟器中运行应用程序。要是一切正常,你会看到一个简单的应用程序会说Hello Lua!。

  要是你没看到这个消息,检查之前的步骤,看看有没有步骤漏了。

  查看Lua

  展开Scripts文件夹,打开AppDelegate.lua。你会看到运行该应用程序的Lua代码。

  你可能会先注意到,没有语法高亮。遗憾的是,对于Xcode中的Lua语法高亮问题,我还没有发现稳定的解决方案(是你发现了,请留言告诉我!)。

  接下来你可能会注意到,没有方括号,不过使用了像UIScreen和UIWindow这些类。那是由于你在使用Lua构建一个AppDelegate类;在苹果和苹果的代码看来,你在使用Objective-C、构建Objective-C类!

  方法名称

  你可能还注意到奇怪的方法名称colorWithRed_green_blue_alpha。要是你熟悉Objective-C,就知道方法名称可以有冒号。Lua中的函数名称不能有冒号。为了补偿这个差异,凡是Objective-C中隔开方法名称的地方,在Lua中都换成下划线。比如说:

  Objective-C中的colorWithRed:green:blue:alpha对应于Lua中的colorWithRed_green_blue_alpha。

  Objective-C中的selectRowAtIndexPath:animated:scrollPosition:对应于Lua中的selectRowAtIndexPath_animated_scrollPosition。

  面向对象的模型

  Lua的另一个问题是,它没有像Objective-C那样的继承体系。Lua中根本没有类。为了克服这个问题,Wax突出显示了放在每个Wax Lua文件最前面的一个函数:waxClass。在默认的AppDelegate.lua中,这一行看起来像这样:

waxClass{AppDelegate, protocols = {UIApplicationDelegate}}

  想通过Lua创建一个Objective-C类,就要使用waxClass{CLASS NAME, PARENT_CLASS}这个函数。你添加到该Lua文件的所有之后的函数(在同一个文件里面)都会作为实例方法,自动添加到新的类。

  AppDelegate.lua的这一行显示,还可以定义你的类定义哪些协议。

  虽然waxClass解决了定义Objective-C可以使用的类这个问题,但还是存在一个问题:由于Lua没有类,它没有像Objective-C那样的动态自变量。为了克服这个问题,Wax自动将每个方法的第一个变量作为类的当前实例。你可以发现,当你查看AppDelegate.lua中的applicationDidFinishLaunching时,第一个变量是自变量,即使Objective-C版的这个方法只有1个变量。然而,如果你非得将类的当前实例作为每个方法的第一个变量来传送,就会很烦人,于是添加了一些语法上的便利(syntactical sugar)。不是使用.操作符在Lua中进行方法调用,而是使用了:操作符:

local view = UIView.initWithFrame(CGRect(0, 0, 100, 100))

  –以下一模一样

view:addSubview(someView)
iew.addSubview(view, someView)

  值得一提的另一个重要方面是,Wax不支持Objective-C属性。Wax迫使Lua和Objective-C只与方法进行联系。

  – 这不行

someView.frame

  – 你而是需要使用getter/setter方法

View:frame() some
View:setFrame(someFrame)

  只用于Lua的变量

  你可以使用点.操作符,为任何Objective-C对象创建成员变量。不像冒号:操作符(用于对Objective-C类/实例调用方法),点.操作符可以针对对象的Lua方面,动态创建成员变量(对象的Objective-C方面对这些变量一无所知)。在对象的生命周期之内,都可以使用成员变量。

  输出到控制台

  AppDelegate.lua还显示了你如何可以编写调试文本、输出到控制台。你可以使用函数puts。

  内存管理

  我之前说过,使用Lua的话,你根本没必要分配、保留和释放内存。你在调用任何初始化器之前,根本不需要调用内存分配。实际上,如果你这么做的话,程序可能会出现内存泄漏。

  太棒了!接下来做什么?

  你已经深入了解了专门针对Wax的Lua的基本知识,就可以准备编写iPhone应用程序了!

  在这个教程的第二个部分,我们将只用几行Lua,就可以编写出拥有刷新按钮的一个Twitter示例应用程序。

  看完这个教程是不是你也有信心打造出自己的《愤怒的小鸟》呢?

小贴士

Lua程序设计语言 是一个简洁、轻量、可扩展的脚本语言。Lua读作/’lua/(噜啊),是葡萄牙语中”Luna”(月亮)的意思。

Lua是一种轻量语言,它的官方版本只包括一个精简的核心和最基本的库。这使得Lua体积小、启动速度快。它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程式里。和许多”大而全”的语言不一样,网路通讯、图形界面等都没有默认提tgcode供。但是Lua可以很容易地被扩展:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。事实上,现在已经有很多成熟的扩展模块可供选用。

Lua的目标是成为一个很容易嵌入其它语言中使用的语言。大多数程序员也认为它的确做到了这一点。

很多应用程序使用Lua作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。这其中包括大话西游II、仙境传说、魔兽世界、战锤40k、博德之门、轩辕剑外传汉之云等,在移动领域最著名的便是《愤怒的小鸟》。

  第四步:UITableViewController数据方法

  我们的应用程序可以启动,这很好,但我们想要显示一些数据。为了显示这些数据,所有UITableViewController必须实施几个方法,告诉设备显示什么数据。其中第一个方法是numberOfSectionsInTableView:,它会返回将在表中显示的群组数量。对该应用程序来说,这一步很容易,因为我们只需要一个表段,即拥有当前趋势的那个表段。

function numberOfSectionsInTableView(self, tableView)
return1
end

  是不是很容易?现在我们得实施tableView_numberOfRowsInSection方法,它告诉设备某个特定的分组会有多少行。对该应用程序来说,这同样很容易,因为我们只有一个表段。记得我们如何用init方法对Lua表进行初始化吗?只要计数该表中的表项数量,就知道该表需要显示多少行:

function tableView_numberOfRowsInSection(self, tableView, section)
return #self.trends
end

  这使用Lua简写方法来计数表中的表项数量。如果你不熟悉Lua表,下面有几个要点:

  1. 大多数语言中被称为词典的东西在Lua中被称为表。

  2. 大多数语言中被称为数组的东西被称为带有序数字键的表。

  3. 数组使用从1开始的索引,而几乎其他每种语言使用从0开始的索引。

  接下来是tableView_titleForHeaderInSection方法。该方法告诉设备显示什么作为某群组的标题。你只要返回某个指定群组的字符串,标题之后会神奇地出现在表行上方:

function tableView_titleForHeaderInSection(self, tableView, section)
if section == 0 then
return Currently Trending Topics
end
return nil
end

  相当简单。现在我们只要往表格填充从Twitter的服务器取来的数据。如果你熟悉Objective-C中的UITableViewControllers,就会认识这下一个方法:

function tableView_cellForRowAtIndexPath(self, tableView, indexPath)
local identifier = TwitterTableViewControllerCell
local cell = tableView:dequeueReusableCellWithIdentifier(identifier) or
UITableViewCell:initWithStyle_reuseIdentifier(UITableViewCellStyleDefault, identifier)
local object = self.trends[indexPath:row() +1] 必须是+1,因为Lua数组从1开始
cell:textLabel():setText(object)
return cell
end

  这个方法要复杂一点。首先,我们定义了对同一种类型,但可能有不同内容的所有表格单元(cell)来说很独特的标识符。这种情况下,我们称之为TwitterTableViewControllerCell。接下来,我们使用Lua简写方法,获得UITableViewCell的实例。注意夹在这两个方法调用之间的or。如果第一个方法调用的结果不是false或nil, cell就被设成第一个方法调用的值。否则,cell会被设成是第二个方法调用的结果。

  我们这么做是为了节省内存。这样一来,设备每次只要为屏幕上10个左右的表格单元分配内存,而不是为数据源里面可能拥有的数千个表格单元分配内存。当然,我们不会有数千行要显示,但这仍是个有必要养成的好习惯。接下来,我们把表格单元的内容设成从self.trends数组的合适部分获取的趋势。我们知道,该索引从来不会超出self.trends的范围,因为我们通过方法tableView_numberOfRowsInSection,将数组大小告诉给了设备。最后,我们返回刚创建的表格单元。如果你现在按Run,它应该看起来像这样:

%title插图%num

  第五步:从Twitter装入数据

  现在说说真正展现Wax魅力的好玩部分:从互联网、或者更准确地说从Twitter的服务器装入JSON数据。先不妨创建一个名为loadDataFromTwitter的新方法。该方法会从Twitter的服务器获取JSON数据,然后为表重新装入新数据。

function loadDataFromTwitter(self)
UIApplication:sharedApplication():setNetworkActivityIndicatorVisible(true) show spinner
wax.http.request{http://api.twitter.com/1/trends.json, callback =function(json, response)
UIApplication:sharedApplication():setNetworkActivityIndicatorVisible(false) hide spinner
if response:statusCode() == 200 then
self.trends = {} Reset the list of trends when the trends are refreshed
for index,value in ipairs(json[trends]) do iterate over a table with numerical keys
table.insert(self.trends, value[name]) append the value to the “array”
end
end
self:tableView():reloadData()
end}
end

  是的,就这么简单。你定义了请求的URL以及请求完毕后执行的回调。Wax自动确认服务器在运行JSON后,会将JSON文本自动转换成Lua表。这使得显示网络活动指示器(设备右上角靠近无线信号指示器的图标)异常容易。返回的JSON看起来像这样。键trends保存一组对象,这些对象包含趋势名称和访问提到该趋势的所有Twitter消息的URL。

  趋势名称存储到self.trends变量里面后,重新装入tableView,它可以再次调用我们之前定义的所有数据方法。这导致趋势在表中显示,非常类似最终产品。

  如果你立即试图运行该应用程序,看上去没什么不同。那是因为该方法从未调用。如果从viewDidLoad:里面调用该方法,我们就能确保总是可以显示最新趋势。把这行添加到viewDidLoad:方法末行的前一行:

self:loadDataFromTwitter()

  如果你点击Run,应用程序看起来有点像这样(你得等几秒钟装入趋势,请留意那个活动指示器!):

%title插图%num

  第六步:添加重新装入按钮

  该应用程序相当棒。不过,要是有重新装入按钮让你可以显示最新趋势,就更好了。幸好,这很容易实现。

  不妨把重新装入按钮放到屏幕的右上角。苹果其实提供了上面有刷新图标的按钮,以图方便,就用这个按钮吧。先开始用viewDidLoad:方法创建一个按钮。把下面这行添加到loadDataFromTwitter调用的前面。

local button = UIBarButtonItem:initWithBarButtonSystemItem_target_action(UIBarButtonSystemItemRefresh, self, loadDataFromTwitter)

tgcode

  这创建了一个UIBarButtonItem:一旦按下按钮,就会对当前对象实例调用loadDataFromTwitter方法。如果你想尝试其他风格。

  我们已创建好了按钮,现在需要把它添加到我们的界面上。使用UITableViewController使得这项工作轻而容易,我们只要对导航栏对象实例调用setRightBarButtonItem:方法,就像这样(这行位于上面给出的那一行后面):

self:navigationItem():setRightBarButtonItem(button)

  如果你各方面都做好了,完成的应用程序应该看起来像这样:

%title插图%num

  第七步:额外好处

  这个项目一个有意思的扩展就是制作更显眼的装入指示器。这可能需要把UIActivityIndicatorView放到其中一个按钮位置。

  结论

  我希望你觉得这篇教程深入浅出地介绍了Wax。如果你想看到有关某个课题的更多Wax教程,欢迎给我留言。