Scripting Objective-C.

Lately I've been interested in embedding other languages in my Cocoa Apps to handle less performance sensitive tasks & to be able to mess with things while the App is running. With the flexibility of the Objective-C Runtime, this turns out to be a simple and fun task.

Scripting Objective-C.

Lately I've been interested in embedding other languages in my Cocoa Apps to handle less performance sensitive tasks & to be able to mess with things while the App is running. With the flexibility of the Objective-C Runtime, this turns out to be a simple and fun task.

Obviously I'm not the first person to have this idea and there are already quite a few projects out there that enable you to interact with ObjC from other languages. The most widely known (Along with their basic messaging syntaxes) being

  • MacRuby: An implementation of Ruby 1.9 on top of the ObjC runtime, formerly sponsored by Apple. (The lead developer has left the project to develop an iOS focused variant called RubyMotion)
    • myInstance = MyClass.instanceWithThis(this, andThat: that)
  • LuaCocoa: An ObjC bridge for Lua
    • myInstance = MyClass.instanceWithThis_andThat(this, that)
  • Nu: A language implemented in ObjC and runs on top of it's runtime.
    • (set myInstance (MyClass instanceWithThis:this andThat:that))
  • io: A smalltalk inspired language that can talk to ObjC via it's built in bridge.
    • myInstance := MyClass instanceWithThis:andThat:(this, that)
    • Or with it's ObjC compatibility syntax: myInstance := [MyClass instanceWithThis:this andThat:that]

All of the above are fine projects, but for the most part are not exactly performant and tend to not worry too much about the “C” part of Objective-C. So for my pet project which happens to be a live coding tool for three dimensional music visualization (I will talk more about that later) it became apparent fairly quickly that they were not the tool for what I was doing. (The ones I gave a serious shot were LuaCocoa & MacRuby)

Enter LuaJIT

LuaJIT is a superior implementation of the Lua language, it adds a lot to it, but the main things it has going for it are in my opinion:

  1. JIT compilation which makes it the fastest dynamic language currently in existence.
  2. A fantastic Foreign Function Interface (FFI) that makes it even easier to talk to C than in Vanilla Lua which is already known for how easy it is to embed.
  3. It’s 100% compatible with Lua 5.1 so there’s a huge library of code available for use with it.

When I discovered LuaJIT I immediately figured that it would be the answer to my needs. However, since it only contains an FFI for C, I would have to do some tinkering in order to make ObjC nice to work with. Shockingly, it only took me an afternoon of work to create a basic LuaJIT → ObjC bridge, and a couple of supplemental afternoons before I had something I felt comfortable releasing. I called it TLC for Tiny Lua Cocoa bridge, because the original version was a single 500 line file. A few revisions later it crept up to ~790 lines, but that can still be considered tiny for a programming language bridge.

So how does my bridge fare in the real world? Well, it is to my best knowledge the fastest Objective-C bridge for any language (Obviously due to how incredibly fast Mike Pall, the developer of LuaJIT has been able to make LuaJIT). It does sacrifice some niceties that existing bridges have, in the name of performance but it is in my opinion very nice to work with.

Here’s an example of a simple Cocoa application written in LuaJIT with TLC:

local objc = require("objc")
objc.loadFramework("AppKit")
pool = objc.NSAutoreleasePool:new()
objc.NSSpeechSynthesizer:new():startSpeakingString(objc.NSStr("Hello From Lua!"))
os.execute("sleep "..2)

So let’s dissect it. local objc = require("objc") loads the TLC module into objc. Then we have to load AppKit’s dynamic library using objc.loadFramework("AppKit"). Once AppKit is loaded, one can simply write objc.ClassName to look up an Objective-C class (it is cached for future usages). Then to send a message to the resulting class object, say selector:withTwoArgs: you would transform the selector to selector_withTwoArgs and use that as a method name to call, resulting in objc.ClassName:selector_withTwoArgs(argOne, argTwo). And that’s the gist of it. There are a lot of other features hidden away, for example class creation/subclassing and block creation. But those are outside of the scope of this article, see the Project Page for details.

Usage examples

To this point, I’ve used TLC for two projects worth mentioning. A remote debugging/inspection tool which lets you log into an app remotely and get a Lua repl with TLC already loaded. And Tranquil, the 3D live coding environment I mentioned above.

Tranquil

Tranquil is a live coding environment whose rendering pipeline and primitive generation in Objective-C and allows you to iteratively create visualizations by manipulating it using Lua at runtime. A couple of examples follow.

The above was implemented with the following code that can be run within Tranquil.

scene:clear()
count = 5000
scene.clearColor = rgb(0,0,0)
p1=buildParticles(count)

local x,y,Ax,wx,px,As,ws,ps,Ay,wy,py
Ax = 300.4
wx = 390.4
px = 123.9
Ay = 390.9
wy = 250.1
py = 200.4
As = 60.9
ws = 130.2
ps = 300.8

d = 0.99
local function A(Aarg,t)
    return Aarg*(t-1)*(1-d)
end

audio:setSmoothingFactor(0.9)
everyFrame(function()
withPrimitive(p1, function()
rotate(0.03, vec3(0,1,0))
wx = audio:mag(2)*100
px = px +0.08
ps = 200 --*audio:mag(5)
py = time()/5
wy = 1000*(audio:mag(6)+0.5)
ws = 50*(audio:mag(13)+1)

    p1:map(function(i,v)
        t = i/count
        x = A(Ax,t) * sin(wx*t+px) + A(As,t)*sin(ws*t+ps)
        y = A(Ay,t) * sin(wy*t+py)
        z = A(Ay,t) * A(Ax,t)*sin(wy*t+ps)
        v.position = vec3(x, y, z)
        v.size=1
        v.color = rgb(1,1,1,0.2)--rgb(0, 0, 0, 0.8)--rgb(sin(y),cos(x),sin(y))
    end)
end)
end)

As you can see it’s fast enough to process 5000 particles per frame using a fairly complex mapping despite being written in a scripting layer on top of the actual application (Actually it should be able to process 10.000+ but I had to cap it at 5000 so that the screen capture would not start dropping frames). So one should be able to write Cocoa applications in Lua without worrying about performance at all. And on top of that, TLC hooks into Lua’s garbage collector, automatically releasing objects when they are no longer referenced on the by any Lua variables.

For more examples you can go to my YouTube Channel which has videos, and my website which has code samples.

So that’s a fun use of embedding a scripting language in a Cocoa app. Now come up with some of your own!

Next is Engineer's Blog!

この記事はカヤックの七夕のイベント「777」に向けたアドベントカレンダー(*)です。 777イベントが開催される7月7日までの期間に、4つの職能ブログ ( エンジニアデザイナーフロントエンドエンジニアディレクター )を横断し、更新して行きます。 テーマは「つくるための三種の神器」。 カヤック有志による、それぞれの切り口で記事を投稿していきます。 では期間中、どうぞお楽しみにください。

HTML5飯