CIMLua: Celedev Objective C API

CIMLua is the Objective-C API for integrating the Celedev execution environment in an application. It offers a rich set of features through a simple and very small set of methods.

About CIMLua

CIMLua is provided as a pair of universal static libraries: libCIMLua.a that contains the Celedev runtime, and libCIMLuaDebugServerIOS.a that handles all communication between the target application and CodeFlow (livecoding, monitoring and debug). CIMLua is fully compliant with modern Objective-C (ARC, subscripting syntax…) and is easy to integrate in any iOS or OS X application.

The Lua Context class CIMLuaContext

CIMLuaContext is the main class of the Celedev Live Programming system. An instance of CIMLuaContext is a complete and standalone Lua runtime, with its own memory and variables, running in its own thread. You can create one or more Lua Contexts depending of the needs of your application.

Lua Context creation and termination

A Lua context is started by creating an instance of CIMLuaContext:

CIMLuaContext* aContext = [[CIMLuaContext alloc] initWithName:contextName mainSourcePackageId:uniquePackageId];

where contextName is for identifying this Lua Context during development, and uniquePackageId is an identifier that allows the Lua context to find unambiguously its specific set of code and resources files at runtime.

If your application only uses one Lua Context instance, or uses several Lua Contexts sharing a same set of code and resource files, you can use this shorter init version:

CIMLuaContext* aContext = [[CIMLuaContext alloc] initWithName:contextName];

When you are done with a Lua Context anymore, you have to call the terminateContextmethod. This will do the necessary cleanup at the system level and will permit the Lua context to be properly deallocated when not referenced by your application anymore.

[aContext terminateContext];

Lua Context Monitor

During the development of your application, you need to create an instance of CIMLuaContextMonitor associated to your Lua Context. The context monitor takes care of the communication between the Lua Context and Celedev CodeFlow development environment, making possible dynamic update of code and resource, as well as Lua code debugging. This context monitor should be removed when you build your application for deployment, so that no attacker can take the control to the Lua Context from the outside, when the app is released to your customers. The context monitor is created right after the corresponding Lua Context. This is typically achieved by code like this one:

    CIMLuaContext* aContext = [[CIMLuaContext alloc] initWithName:nameForDebug mainSourcePackageId:uniquePackageId];
#ifdef DEBUG
    CIMLuaContextMonitor* contextMonitor = [[CIMLuaContextMonitor alloc] initWithLuaContext:aContext connectionTimeout:5];
#endif

The connectionTimeout parameter defines a time interval during which the execution of the associated Lua Context is suspended after the context monitor is created. This suspension gives the opportunity to the development environment (IDE) to create a connection with this context monitor and load code or set some breakpoints in the Lua Context. If, after the timeout, no IDE connection has been established, the Lua Context execution continues normally, using the code and resources embedded in the application on the device.

Running code in a Lua Context

A set of methods are defined in CIMLuaContext for loading and running Lua code in the Lua context. Code can be loaded from a NSString, from a file at a given URL, or from a named Lua module. The module-based method is by far the most common and preferred one, as it enables the dynamic code update and thus is the key for Live Programming.

Loading Lua modules

Asynchronous Lua module loading

- (void) loadLuaModuleNamed:(NSString*)luaModuleName withCompletionBlock:(void(^)(id result))completionBlock;

loadLuaModuleNamed:withCompletionBlock: loads the Lua module with name luaModuleName into the target Lua Context and executes the code for this module. Loading and execution of the module are asynchronous and done out of the application's main thread. When the execution is complete, the completion block is invoked in the application's main thread and passes back to the caller the first result of the module (converted to an objc id).

If at any moment later the loaded module is updated by the Responsive Programming system, the completion block will be invoked again to notify the application of the - possibly updated - module result.

The code sample below shows the application:didFinishLaunchingWithOptions: method of an application where the root view controller is written in Lua:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Create a Lua Context for this application
    _luaContext = [[CIMLuaContext alloc] initWithName:@"Root View Controller" mainSourcePackageId:@"Collections"];

    // Create a Remote Monitor associated with this context, for enabling the connection with the development environment
    _luaContextMonitor = [[CIMLuaContextMonitor alloc] initWithLuaContext:_luaContext connectionTimeout:5];

    // Create the application window (standard stuff)
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];

    // Run the code for this Lua context
    [_luaContext loadLuaModuleNamed:@"ViewControllerMain" withCompletionBlock:^(id result) {

        if ([result isKindOfClass:[UIViewController class]])
        {
            self.window.rootViewController = result;
        }
    }];

    [self.window makeKeyAndVisible];

    return YES;
}

In this example, the completion block simply sets the application window's root view controller with the result of the Lua module named ViewControllerMain if this module returns a UIViewController instance, as expected. If the ViewControllerMain is updated at some point, the root view controller will be automatically updated and so the new view controller will immediately be visible in the application.

Synchronous Lua module loading

Lua modules can also be loaded synchronously. This is necessary when entities defined by a Lua module are needed early in the application lifecycle, for exemple when a Lua module defines a Class Extension and instances of the extended class are created by the nib or storyboard of the application's first screen. In such case, you will use the synchronous method loadLuaModuleNamed:

- (id) loadLuaModuleNamed:(NSString*)luaModuleName;

loadLuaModuleNamed: loads the Lua module with name luaModuleName into the target Lua Context, executes the top-level code of this module, and returns the module's first result, converted to an Objective-C id.

Lua Module Unloading

In rare circumstances, you may want a previously-loaded module to be seen as not loaded, for example because your application is in a state in which you want the completion block for the Lua module not to be invoked anymore, you can call unloadLuaModuleNamed:withCompletionBlock:. After the completion block of this unload method is called, the application is guaranteed that the Lua context won't invoke anymore any of the blocks associated with the (re)loading of the Lua module with the given name.

Loading code from strings

Other methods for running Lua code in a Lua Context include two methods where the code is passed as a NSString parameter (one synchronous and on asynchronous):

// Synchronous execution of the Lua code
- (id) executeLuaSourceCodeString:(NSString*)sourceCodeString;

// Asynchronous execution of the Lua code
- (void) executeLuaSourceCodeString:(NSString*)sourceCodeString withCompletionBlock:(void(^)(id result))completionBlock;

These methods allow the caller to dynamically create Lua code that will be executed by the Lua Context. Note that calling the synchronous executeLuaSourceCodeString: from the application's main thread is not recommended as the application won't be able to process user events until the execution of the provided Lua code completes.

Accessing Lua values from Objective-C

Global variables of a Lua Context can be easily get and set from Objective-C, using Objective-C subscripting syntax.

For example:

_luaContext [@"level"] = @3;                // equivalent to Lua: level = 3
_luaContext [@"isActive"] = @YES;           // equivalent to Lua: isActive = true
_luaContext [@"prompt"] = @"Hello world";   // equivalent to Lua: prompt = "Hello world"
_luaContext [@"mainViewController"] = self; // set Lua global variable mainViewController to objc id self

NSNumber* gameLevel = _luaContext [@"level"]; // here we are confident that the Lua global "level" contains a number 

Lua values read from the Lua context are converted into Objective-C ids, and objc ids written to the . The table below lists the type correspondance between both languages.

Lua type Objective-C class
nil nil
boolean NSNumber (@YES or @NO)
number NSNumber
string NSString
table CIMLuaTable
function CIMLuaFunction
lua thread CIMLuaValue
lua object id<CIMLuaObject>
objc reference other objc instance or class

Lua tables are a common case when communication between Lua and Objective-C. Therefore CIMLuaTable supports the Objective-C subscripting syntax and has a set of methods for converting a Lua table to a NSArray or NSDictionary and for enumerating the keys and values of a Lua table.

Lua functions are translated into CIMLuaFunction instances that can be called from Objective-C. However, as the number and types of parameters for a given Lua function is unknown from the system, no check is performed when a Lua function is called from Objective-C. For this reason, you should usually avoid to call Lua functions from Objective-C and rely on Lua object methods to trigger actions in a Lua Context.

Calling Lua methods from Objective-C

When using Celedev Object Framework for Lua, object methods defined in Lua can simply be called from Objective-C as if they were plain standard Objective-C methods.

However, in order to be called, these methods have to be known from the Objective-C compiler when the application is built. This is achieved in two different way depending of where the Lua method is defined:

  • if the Lua method overrides a method defined in one of its ancestors Objective-C classes or implements a method defined by an existing Objective-C protocol, it can be called directly and doesn't need any further declaration; for example, if a Lua class conforms to a delegate protocol, any method in this protocol can be called transparently by the delegating object.
  • if the Lua class define its own methods that will be called from Objective-C, these methods need to be declared to the Objective-C compiler. This is done by defining a protocol declaring the exported methods and properties, and by registering this protocol to the Objective-C runtime by calling the function CIMLuaRegisterObjcProtocol. The code sample below illustrates how to use it.
// The LuaAccount class is defined in file "lua-account.lua"

@protocol LuaAccount
+ (id<LuaAccount>) createAccount:(double)firstDeposit;
- (void) printInfo;
- (void) deposit:(double)value;
- (void) withdraw:(double)value;
@end

void testLuaAccount (CIMLuaContext* luaContext)
{
    CIMLuaRegisterObjcProtocol (@protocol(LuaAccount));

    [luaContext loadLuaModuleNamed:@"lua-account" withCompletionBlock:^(id result) {

        Class<CIMLuaObject, LuaAccount> accountClass = result;

        id<LuaAccount> account1 = [accountClass createAccount:100];

        [account1 deposit:150.00];
        [account1 printInfo];
        [account1 withdraw:20];
    }];
}

Declaring invocation contexts for Objective-C classes

A Lua Context tries to be as unobtrusive as possible regarding the application's main thread. Therefore it runs Lua code in separate threads (or more specifically in GCD dispatch queues) whenever possible. However some Objective-C classes are not thread-safe and shall have their methods called in a specific thread, generally the main thread.

For such classes, CIMLua has a notion of invocation context that enforce the calls to methods of a given class to be done in a specific thread context.

Note that invocation contexts for most classes defined in the iOS SDK are already defined by the SDK CodeFlow Bindings Library. So you will only need to declare invocation contexts in rare cases and mostly for your own classes. And be careful when declaring invocation contexts: like any other operation involving threads synchronisation, invocation contexts can cause a deadlock if misused.

The invocation context for a class is declared with a class method of CIMLuaContext:

+ (void) declareObjcMethodsInvocationContext:(dispatch_queue_t)invocationContext forClasses:(Class)aClass, ... NS_REQUIRES_NIL_TERMINATION;

This method shall be called before the creation of the first CIMLuaContext in the application. Typically, a +load method is a good place for this. For example:

@implementation AppDelegate

+(void)load
{
    // Declare base classes that shall be executed in the application's main thread
    [CIMLuaContext declareObjcMethodsInvocationContext:dispatch_get_main_queue() 
                                            forClasses:[UIView class], [UIViewController class], [UICollectionViewLayout class], nil];
}

// ... other methods

@end    

This declares that UIView, UIViewController, UICollectionView and all their subclasses will have their methods called from Lua always invoked in the application's main thread.

Currently, only the main thread is supported as invocation context, so you shall always pass dispatch_get_main_queue() in the invocationContext parameter.

The sandbox API

By default, a CIMLuaContext has access to the whole set of Objective-C classes and methods available in the application: application classes, SDK frameworks… Generally this is the expected behavior, but it can occur that you want to restrict the set of external APIs usable from the Lua code for some reason. For supporting this kind of access restrictions, a `CIMLuaContext' can be switched to sandbox mode.

Sandbox mode in CIMLua works in white list mode. This means that - when sandbox mode is activated - the Lua Context will have access only to the classes, methods or properties that have been explicitly permitted to the Lua Context.

The sandbox API is composed of a small set of methods:

@property (nonatomic, getter=isSandboxActive) BOOL sandboxActive;

- (void) permitLuaAccessToClasses:(Class)exposedClass, ... NS_REQUIRES_NIL_TERMINATION;
- (void) permitLuaAccessToClass:(Class)exposedClass restrictedToIndexKeys:(NSString*)luaKey, ... NS_REQUIRES_NIL_TERMINATION;

Post a Comment