iOS Programming with Lua

Matthew Burke, Bluedino Software

In this article I will discuss three methods for using Lua to build iOS apps. The techniques range from using Lua to build the entire application (Corona) to using Lua as a component for scripting your application (do it yourself or Wax). Before getting into the details, there are two main questions to answer:

  1. Why use Lua?
  2. Will Apple let you use Lua?

The answers to these questions are intertwined.

In case you landed here without knowing anything about Lua, let me hit the high points of the language. If you already are familiar with Lua, you can skip ahead.

About Lua

Lua is a fast, lightweight, embedded scripting language. It is similar to languages such as JavaScript, Ruby or Python. Many of its users, including myself, feel Lua is a particularly clean and elegant language.

Lua was created in 1993 by Roberto Ierusalimschy, Waldemar Celes and Luiz Henrique de Figueiredo at the Pontifical Catholic University in Rio de Janeiro, Brazil. It is used in a range of applications including Mi Casa Verde, Adobe Lightroom, Celestia, lighttpd, LuaTeX, nmap, Wireshark, Cisco's Adaptive Security Appliance, pbLua, and a ton of games including Grim Fandango, World of Warcraft, Tap Tap Revenge, and many others. Lua's license is a variation of the MIT License—this means that there are essentially no hurdles to including Lua in both commerical and non-commerical projects.

Lua's main data structuring mechanism is the table—a combination resizable array and hash. Listing 1 shows a table we might use in a hypothetical application to keep track of automobiles and their gas mileage. We can store information about the automobile using string keys such as license and make. A series of mileage readings are stored using integer indices.

Listing 1: A 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!)
In Lua, array indices start at one, not zero. Comments are indicated by '--' and run to the end of the line.

Out of the box, Lua is neither an object-oriented (OO) programming language nor a functional programming language. Rather, it provides a small set of mechanisms with which you can build your own higher-level features. A number of different object systems have been built in Lua including more traditional OO systems as well as classless OO systems (à la languages like Self or Io). Lua supports first-class functions and lexical closures and has a meta-programming facility (known as metatables and metamethods). Lua can be used in a functional programming style

For a gentle introduction to OO in Lua, read Programming in Lua (in fact, PIL is a very well-written book on Lua in particular and programming in general). You should also look at the several examples available on Lua wiki.

If you like drinking from a firehose, Listing 2 shows one possible implementation for a linked list class. The table stored in the variable List serves as a metatable for all linked list objects. It provides a fall-back location for looking up table indexes and thus serves as a class dispatch mechanism. The line "List.__index = List" is what allows us to create methods for our list objects. Methods are implemented as functions stored in the List metatable. When we attempt to call one of these functions on our list object, the lookup mechanism will lead us to the function defined on the List metatable and that's what will get run.

The code shows a number of additional features of Lua: multiple assignment (and functions can return multiple results), syntactic sugar for method calls (the ':' notation, this performs the common technique—used in languages ranging from Python to Objective-C—of rewriting the function call to have an additional parameter which points to self).

Listing 2: Linked List Class
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
    return self.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())

I'm sure I've left out the really interesting/important things (such as lexical closures), but this at least gives you a little taste of Lua. There are more samplings below when we get to actual iPhone coding. For further details on Lua, see the web site.

Can I Script on iOS?

Of the two questions listed at the beginning of this article, the most important question to address is are you allowed to use Lua (or any interpreted language) on the iPhone? After all, early on the iPhone Developer Program License Agree stated that "[n]o interpreted code may be downloaded or used in an Application except for code that is interpreted and run by Apple's Documented APIs and built- in interpreter(s)."

In fact, an earlier version of this article was tabled when Apple made changes to the developer license (circa April 2010) which forbade developing iOS applications in any language other than Objective-C and Javascript (the Javascript could be used either to build web apps or native apps via the UIWebView). Recently (September 2010), Apple again changed the developer license to allow the use of scripting languages..

There are still several important constraints in place. In particluar, although you can use Lua and other scripting languages, you cannot create an application where users could download plugins for your app from your website (in-app purchasing anyone?), nor can you allow the user to write scripts, download scripts, etc. There are (and have been, e.g. Tap Tap Revenge) quite a number of apps available on the app store that use Lua as well as other languages.

Of course, creating a plugin system and allowing users to write scripts are two major use cases for including a language like Lua in your application, so what's left? Plenty!

Why Use Lua for iOS development?

Although you cannot expose a plugin system to the end user, nor can you give her the ability to write her own scripts, you can still develop your system using a plugin architecture! This can both speed up initial development as well as be a big help when it's time to add functionality for the next version.

There are other benefits from using Lua. It allows you to develop using rapid prototyping (beware my pet peeve: don't devolve into seat-of-your-pants programming), reduces/eliminates the need to worry about memory allocation, allows more of your team to participate in development (many Lua projects have non-programmers writing code), makes it easier to tune your application, and provides a powerful persistence mechanism.

In short, using Lua can reduce development time and lower entry barriers. And it's just plain fun!

Assuming you're sold you on the idea of using Lua, how do we go about it?

Corona

Ansca Mobile's Corona allows you to develop your iOS app entirely in Lua. And it's not just for iOS. You can also develop apps for Android. In fact, you can use the same source code to build both an iOS and an Android app. This adds a compelling reason to use Lua (and, in particular, to use Corona): the ability to easily build cross-platform applications.

Listing 3 is the complete source for an app.

Listing 3: main.lua from the Swirly Text app
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 > w or message.x < 0 then
      dx = -1 * dx
      counter_spin = true
   end
   if message.y > h or 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 apps are developed using your favorite text editor—I use Emacs. All Lua source code and any necessary resources (images, sounds, and data) must reside in a single directory and Corona expects a Lua file, main.lua which is where your app starts executing. You test your code in Corona's simulator which runs on both Intel and PPC Macs. Figure 1 shows my Corona 'IDE': namely Emacs with two windows (a Lua file and the project directory), the Corona terminal (you can print debugging info to the terminal), and the Corona simulator.

Figure 1: My Corona 'IDE'

Clockwise from the left: Corona simulator, Emacs with two windows (a source file and project directory), the Corona terminal (for debugging info).

When you are ready to run your app on actual hardware, you use the Corona Simulator's Open for Build option. For an iOS build, you must have a provisioning profile (either development or distribution)—yes, Virginia, you do need a membership in the iOS Developer Program—which is uploaded, along with your app's source and resources, to Ansca's servers. A compiled app is returned to you. For Android builds, you will need a suitable signing certificate. Again the build process is handled by uploading your source to Ansca's servers. You do not need to have the Android SDK installed.

I haven't done any in-depth poking around, but both the .apk file from the Android build process and the iOS .app bundle contain a file containing all your Lua code pre-processed in some fashion. Approximately seven seconds of examination suggests that it's not standard byte-compiled Lua code, but I'm guessing it must be a similar format.

Corona's event system lets you handle touches (including multi-touch), access the GPS and the acceleratometer, handle animation, and define custom events. There is a powerful graphics system which allows you to draw circles, rectangles and text. They've recently added polylines so you can draw regular lines and polygons. You can also display images. Corona allows you to group these objects and do transformations on them. Listing 4, an excerpt from a Solar System simulator, shows a (crude) example of grouping graphics objects. Other Corona features include playing audio and video clips, a cryptography library, networking using the LuaSocket library, and access to SQLite databases (using LuaSQLite). There is some access to native widgets including textfields, alerts and activity indicators. There's a nice feature where you can overlay a web view for doing things like login screens and a sample application provides a library for connecting to Facebook. The last thing I'll mention is that there is a (more expensive) game edition that includes the Box2D physics engine, sprites and some OpenFeint functionality such as leaderboards.

Listing 4: excerpt from Solar System app
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
By passing a table as a function's paramter, we can make use of named parameters and default values. Thus, the idiom of local radius = params.radius or planetRadius()

Corona gives you a lot. However, there are still a lot of things missing with Corona. The biggest item is the limited access to native controls. Not to mention the access that is provided is awkward to use because of limited support in Corona's simulator. In the simulator, native alerts and activity indicators are implemented using OS X equivalents, rather than the iOS widgets. However, text fields, text boxes and web popups are unusable when running in the simulator. This makes development, to say the least, painful.

Finally, there is no mechanism to access the Objective-C API except for what ANSCA has specifically provided. Not only does this mean you cannot access large portions of the standard libraries, but you cannot make use of third-party libraries like Three20, or one of the mobile ads APIs. Of course, with the release of the Android version of Corona, you may not want to access the Objective-C API since it limits (or complicates) your ability to do multi-platform applications. It would be nice, however, to be able to add in Lua extensions that use Lua's C API, as many of these are multi-platform.

I've found the ANSCA staff to be quite helpful and responsive to questions posted in the forums on their website. With the release of version 2.0 (September 2010), Corona costs $249 per developer per year. The Game Edition is $349 per developer per year. Ansca's website indicates that the game edition prices is pre-release. I assume that means it will be higher when it is officially released.

DIY

Including the Lua interpeter in your iOS app is simple. Open an Xcode project and add the Lua source files (except lua.c and luac.c—source for the command line programs). Compile. You can now make use of the standard Lua C API to create an interpreter and run some code. An example project, iLua, may be downloaded from http://github.com/profburke/ilua. iLuaShell is a simple, view-based application that presents the user with two text fields—an editable one where the user can enter Lua code and a non-editable one where the results of evaluating the Lua code are displayed.

The work is done in the method, evaluate, which is shown in Listing 5. The method retrieves the text of the first text field, hands it off to the Lua interpreter which parses and executes it, and then sets the Lua output as the text of the output field.

Listing 5: LuaTrial's evaluate method.
-(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>";
    } else {
        NSString *outputNS = [NSString string];
        for (int i = nresults; i > 0; i--) {
            outputNS = [outputNS stringByAppendingFormat:@"%s ",
                                               lua_tostring(L, -1 * i)];
        }
        lua_pop(L, nresults);
        output.text = outputNS;
    }
}

Note the error checking and handling is minimal. A little additional effort could yield a nice Lua shell—not that you could actually put it in the App Store...

What are the drawbacks to this approach? The biggest one is the lack of a bridge to Objective-C. Ideally we want to call Objective-C methods from Lua. Going the other direction is important also. Being able to code callbacks and delegate methods in Lua would be a big win.

Pursuing that leads us to...

iPhone Wax

Powerful interoperability between Lua and Objective-C is the star attraction of iOS Wax by Corey Johnson. Using Wax, you can easily subclass Objective-C classes in Lua! Listing 6 shows a Wax implementation of a custom view controller. In particular, this code is a port of the application described in the DIY section. Details after the listing.

Listing 6: RootViewController.lua
waxClass{'RootViewController', UI.ViewController }

function init(self)
        self.super:init()

    self.input = UI.TextView:initWithFrame(CGRect(20, 20, 280, 114))

    self.output = UI.TextView:initWithFrame(CGRect(20, 184, 280, 225))

    local evalButton = UI.Button:buttonWithType(UIButtonTypeRoundedRect)
    evalButton:setTitle_forState('Evaluate', UIControlStateNormal)
    evalButton:setFrame(CGRect(200, 142, 100, 32))
    evalButton:addTarget_action_forControlEvents(self, 'eval:',
                            UIControlEventTouchUpInside)
    self.evalButton = evalButton

    self:view():addSubview(self.input)
    self:view():addSubview(self.output)
    self:view():addSubview(self.evalButton)

    return self
end

function eval(self, sender)
    self.input:resignFirstResponder()

    local code, errmsg = loadstring(self.input:text())
    if not code then
        self.output:setText(errmsg)
        return
    end

    local success, result = pcall(code)
    print('result is ' .. tostring(result))
    if not success then
        self.output:setText('Error: ' .. tostring(result))
    else
        self.output:setText(tostring(result))
    end

end

The waxClass function essentially defines a new Objective-C class. In this instance we are defining a class named RootViewController which is a sub-class of UIViewController. (In Wax, the Objective-C classes have been put into namespaces, hence UI.ViewController, rather than UIViewController). The Lua representation of instances of this class is a table (well, really a userdata, but you can think table...), hence items like self.input are Lua table fields and not Objective-C properties. To access properties you use setters and getters, e.g. self.output:setText(). If you're like me, this will trip you up until you embarass yourself by asking about it in the mailing list. Aftewards, you won't mix it up again. (Actually, the people on the mailing list are quite nice.)

Wax classes can also implement protocols. For instance, the Wax sample project, States, demonstrates handling a UITableView with two custom UITableViewController classes. Each of which implement the UITableViewDelegate and UITableViewDataSource protocols. Listing 7 is a minor variation on this theme and presents a class that implements the UITableViewDataSource protocol for a multi-section table.

Listing 7: SortedDataSource.lua
waxClass{'SortedDataSource', NS.Object, protocols = {'UITableViewDataSource'}, }


function init(self, source_table)
    self.source_table = source_table
    return self
end


function numberOfSectionsInTableView(self, tableView)
    return #self.source_table.headers
end


function tableView_numberOfRowsInSection(self, tableView, section)
    local index = self.source_table.headers[section+1]
    return #self.source_table[index]
end


function tableView_cellForRowAtIndexPath(self, tableView, indexPath)
    local identifier = 'TableViewCell'
    local cell = tableView:dequeueReusableCellWithIdentifier(identifier)
    cell = cell or UI.TableViewCell:initWithStyle_reuseIdentifier(UITableViewCellStyleDefault,
                                                      identifier)

    local key = self.source_table.headers[indexPath:section()+1]
    local component = self.source_table[key]
    local player = component[indexPath:row()+1]
    cell:setText(player[1] .. ' ' .. player[2] .. ' ' .. player[3])

    return cell
end

function tableView_titleForHeaderInSection(self, tableView, section)
    return self.source_table.headers[section+1]
end
Note the toll-free conversion of Lua strings to Objective-C strings in functions such as tableView_titleForHeaderInSection.

The uniform naming scheme Wax uses allows you to easily predict the name to use for accessing an Objective-C function from Lua. Wax comes with a TextMate bundle which makes it very easy to manipulate the Objective-C calls. For example, you can paste method signatures copied from Xcode’s documentation and have them automatically transformed into Lua calls. (I’m debating writing Emacs functions to do this or just drink the Kool-ade and start using TextMate.)

Wax has a number of other goodies including extensions for working with SQLite, easy HTTP requests, XML and JSON handling, and working with Core Graphics transforms and gradients. In addition, recent updates include the ability to write the App delegate in Lua (rather than having a small Objective-C implementation that launches Wax), and the ability to run tests from the command line (in a headless simulator). Perhaps the most interesting (and powerful) new development is that Wax now provides an interactive console so you can telnet into the simulator (or a device!) and interact with a running application: tweak its parameters or inspect its current state.

Wax is open source and is also licensed with an MIT-style license. The project is being actively developed and used by a number of developers.

Summary

Lua-powered apps are available in the app store. The Ansca forum for listing Corona apps has over 150 topics (I haven't read them all...they may not all announce new apps). Reading through the Wax mailing list, you'll see several developers announcing apps they've written using Wax, and there are likely to be many others who have not posted to the list. And there are many apps that have taken the DIY approach.

Corona offers a very nice alternative for building iOS apps, but only if you don't need native UI elements. It's great that they've added native text fields, but the fact that you can't see them in the simulator is a real show-stopper. But throw its Android capabilities into the mix, and Corona is certainly worth considering. I like using Corona and I hope/expect to see lots of improvements over time. Just to weasel out of making an endorsement, I should add that although I have not had any major problems building apps with Corona, the dependency on Ansca and their servers to do builds is something you should seriously think about.

Of course the DIY approach is the complete opposite: you have total control. But if you need a lot of interaction between your Lua code and your Objective-C code, doing the bindings can be a fair amount of effort. Johnson’s Wax does a fantastic job of bridging Lua and Objective-C. Wax also plays well with Lua C libraries—something which Corona doesn’t handle.

Although they may not go as far as we’d like, the recent changes to the iOS developer agreement mean that you can now use Lua in iOS development without the stress of worrying that you’re setting youself up for app rejection. I believe the range of techniques available for using Lua in your iOS project means that using Lua will often be a useful tool for successfully completing your iOS project. Given the active communities surrounding Corona and Wax, as well as the ease of plotting your own course if you want more direct control of your use of Lua, I encourage you to take advantage of this gem of a language.

References

Mathew Burke

Mathew Burke

Published on 23 Sep 2010