Swift Interoperability

This tutorial explains how to set up the Xcode project for writing a Marta plugin in Swift. The content is very similar to the Objective-C Interoperability tutorial, and I will refer to relevant sections of it. Yet, there are a few significant differences, and I want to cover them here.

Setting up the project

Project configuration is exactly the same as in the Objective-C tutorial. Please follow the steps described there, but set the product name to swiftinterop.

We do not need the Objective-C files Xcode created for us, and you can move swiftinterop.h and swiftinterop.m to Trash. Instead, create a plugin.swift (right-click on the swiftinterop folder, “New File…” → “Swift File” → “Next” → plugin.swift → “Create”).

Xcode will suggest you configure an Objective-C bridging header. We will need it, so click on “Create Bridging Header”.

Adding a bridging header

Swift part

Swift cannot import C headers directly. To call Lua C API, we need to add an import to the bridging header.

Open swiftinterop-Bridging-Header.h and add the following import directive:

#import "lauxlib.h"

Now we can add an entry point, together with our helloWorld() function.

And here is plugin.swift:

import Cocoa

private let helloWorld: lua_CFunction = { L in
    let alert = NSAlert()
    alert.messageText = "Hello from Swift!"
    alert.runModal()
}

@_cdecl("luaopen_libswiftinterop")
public func luaopen_libswiftinterop(L: OpaquePointer) -> CInt {
   let library: [(String, lua_CFunction)] = [
       ("helloWorld", helloWorld)
   ]

   lua_createtable(L, 0, Int32(library.count))
   for (name, function) in library {
       lua_pushcclosure(L, function, 0)
       lua_setfield(L, -2, name)
   }

   return 1
}

As you have probably noticed, there are some differences, comparing to C and Objective-C:

  • First of all, Swift does not understand function-like macros such as luaL_newlib(). So we initialize the library manually by creating a table and filling it with functions we want to expose.
  • Also, Swift cannot reference opaque structures, so we get an OpaquePointer instead of lua_State*.
  • Finally, we used the @_cdecl attribute, which exposes the function to C.

We implemented helloWorld() as a closure, yet it is not required. Instead, you can declare an ordinary function:

private func helloWorld(L: OpaquePointer!) -> CInt {
    let alert = NSAlert()
    alert.messageText = "Hello from Swift!"
    alert.runModal()
    return 0
}

That’s it! You can build the library the same way as explained in the Objective-C Interoperability tutorial.

Lua part

The Lua part is also the same:

marta.expose()

plugin {
    id = "marta.example.swiftinterop",
    name = "Swift Interoperability Test",
    apiVersion = "2.1"
}

local interop = require "libswiftinterop"

action {
    id = "test",
    name = "Test Swift Interoperability",
    apply = function(context)
        interop.helloWorld()
    end
}

After a restart, you should be able to run the “Test Swift Interoperability” test.