求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
开发"愤怒的小鸟"的Lua语言:Wax框架详解
 

发布于2012-5-3

 

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

 Wax是什么?

Wax for iPhone这种框架在开发时,旨在把Lua脚本语言和原生Objective-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:

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

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

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

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

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

现在,执行下列操作:

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

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

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

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

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

设置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/copy-scripts.sh。

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

改动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,可以很方便的嵌入别的程式里。和许多"大而全"的语言不一样,网路通讯、图形界面等都没有默认提供。但是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,它应该看起来像这样:

第五步:从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

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,应用程序看起来有点像这样(你得等几秒钟装入趋势,请留意那个活动指示器!):

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

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

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

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

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

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

self:navigationItem():setRightBarButtonItem(button)

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

第七步:额外好处

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


相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

 
分享到
 
 
     


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...