Interfacing Lua Objects with Objective-C

Objects defined with the Celedev Object Framework are interoperable with Objective-C. In particular, object methods defined by a Lua class can be called from Objective-C once they have been published. And, more generally, a Lua object can be used in Objective-C like any other object while remaining associated to its original Lua Context.

Publishing Lua Methods to Objective-C

By default, Lua methods declared with the Celedev Object Framework are not visible from Objective-C. Trying to invoke a not-visible method on a Lua object from Objective-C results in a unrecognized selector exception in the application.

The main reason for this is that Lua is by far more flexible than Objective-C regarding the number and types of parameters accepted by a given function, so the precise types of the expected parameters and return value expected by the Lua method shall be known before calling it from Objective-C. From an application design perpective, this also helps to keep a clean interface between a Lua Context and the Objective-C runtime, by enforcing the specification of which methods are public, and which are private to the Lua Context.

So, in order to be visible from Objective-C, a method shall be published. Publishing a Lua method consists mainly in declaring an equivalent Objective-C interface for this method, which makes it de facto public.

Publishing a Lua method is easy.

Publishing via inheritance

First and most important, a class inherits all published methods from its superclass. Therefore, when a class overrides a published method, the overridden method is automatically considered as published (as this is the expected behavior in any Object-Oriented system).

Specifically if the superclass (or an ancestor class) is an Objective-C class, all methods defined by this superclass are published. In addition, if the superclass conforms to Objective-C protocols, all methods declared in these protocols are published as well, including optional methods not implemented by the superclass.

In the following example, the defined class is a subclass of UITableViewController, so all methods implemented here are published to Objective-C:

local CheckerTableViewController = class.createClass ("CheckerTableViewController", objc.UITableViewController)

local myCellId = 'myCellId'

-- loadView is declared by class UIViewController
function CheckerTableViewController:loadView ()
    -- create the tableView
    local tableView = objc.UITableView:newWithFrame_style (objc.UIScreen:mainScreen().applicationFrame, 
                                                           UITableView.Style.Plain)
    tableView.autoresizingMask = UIView.Autoresizing.FlexibleHeight + UIView.Autoresizing.FlexibleWidth
    tableView.delegate = self
    tableView.dataSource = self
    tableView:registerClass_forCellReuseIdentifier(CheckerCell, myCellId)

    self.tableView = tableView
end

-- These methods are defined in the UITableViewDataSource protocol 
function CheckerTableViewController:numberOfSectionsInTableView (tableView)
    return 1
end

function CheckerTableViewController:tableView_numberOfRowsInSection (tableView, sectionIndex)
    local tableItems = self.tableItems
    return tableItems and tableItems:count() or 0
end

function CheckerTableViewController:tableView_cellForRowAtIndexPath(tableView, indexPath)
    local cell = tableView:dequeueReusableCellWithIdentifier (myCellId)
    cell.itemInfo = self.tableItems [indexPath.row + 1]
    cell:setAppearance()
    return cell
end

-- This method is defined in the UITableViewDelegate protocol 
function CheckerTableViewController:tableView_didSelectRowAtIndexPath (tableView, indexPath)
    local selectedCell = tableView:cellForRowAtIndexPath(indexPath)
    -- handle selection
    -- ...
end

Publishing via protocols

A Lua class can declare at any time that it conforms to a given Objective-C protocol or list of protocols by calling its class method publishObjcProtocols and specifying a protocol name or an array of protocol names.

Publishing an Objective-C protocol in a class publishes every method of this class whose name matches a method or property defined in the protocol. These methods may or may not exist at the time the method publishObjcProtocols is called: if methods declared in the protocol are implemented later by the class, those methods will be published as well. If a method is declared by the protocol and not implemented in the class or in one of its superclasses, calling this method from Objective-C will result in a unrecognized selector exception.

Note that publishing an Objective-C protocol in a class also impacts the published methods of its subclasses, since subclasses inherit published methods from their superclass!

Example of use:

local GestureController = class.createClass ("GestureController", objc.UIViewController)

-- declare that GestureController conforms to the UIGestureRecognizerDelegate protocol
GestureController:publishObjcProtocols {"UIGestureRecognizerDelegate"}

function GestureController:viewDidLoad ()

    local gestureView = self.view

    -- Create a pinch gesture recognizer
    local pinchRecognizer = objc.UIPinchGestureRecognizer:newWithTarget_action (self, "handlePinchGesture")
    -- register as gesture recognizer delegate (ok since we conform to @protocol(UIGestureRecognizerDelegate))
    pinchRecognizer.delegate = self
    gestureView:addGestureRecognizer (pinchRecognizer)
    self.pinchRecognizer = pinchRecognizer

    -- Create a rotation gesture recognizer
    local rotationRecognizer = objc.UIRotationGestureRecognizer:newWithTarget_action (self, "handleRotationGesture")
    -- register as gesture recognizer delegate (ok since we conform to @protocol(UIGestureRecognizerDelegate))
    rotationRecognizer.delegate = self
    gestureView:addGestureRecognizer (rotationRecognizer)
    self.rotationRecognizer = rotationRecognizer
end

-- a UIGestureRecognizerDelegate method (hence published to Objective-C)
function GestureController:gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer(recognizer1, recognizer2)
    return ((recognizer1 == self.pinchRecognizer) and (recognizer2 == self.rotationRecognizer)) or
            ((recognizer2 == self.pinchRecognizer) and (recognizer1 == self.rotationRecognizer))
end

Publishing action methods

A common case where a class needs to publish individual methods for which no Objective-C protocol is defined is the Target-Action design pattern. Registered action methods need to be called from Objective-C and therefore Celedev Object Framework includes a dedicated class method for this use case: publishActionMethod.

Here is an example of use in the continuation of the GestureController class above that registers two action methods handlePinchGesture and handleRotationGesture in its viewDidLoad method:

function GestureController:viewDidLoad ()
    -- ...
    local pinchRecognizer = objc.UIPinchGestureRecognizer:newWithTarget_action (self, "handlePinchGesture")
    -- ...
    local rotationRecognizer = objc.UIRotationGestureRecognizer:newWithTarget_action (self, "handleRotationGesture")
    -- ...
end

function GestureController:handlePinchGesture (gestureRecognizer)

    if gestureRecognizer.state == UIGestureRecognizerState.Began then
        -- ...
    elseif gestureRecognizer.state == UIGestureRecognizerState.Changed then
        -- ...
    else
        -- ...
    end
end

function GestureController:handleRotationGesture (gestureRecognizer)

    if gestureRecognizer.state == UIGestureRecognizerState.Changed then
        -- ...
    elseif gestureRecognizer.state ~= UIGestureRecognizerState.Began then
        -- ...
    end
end

-- Publish the action methods
GestureController:publishActionMethod ("handlePinchGesture")
GestureController:publishActionMethod ("handleRotationGesture")

Using a Lua object in Objective-C

A Lua object, i.e. an object of a class defined in the Lua Object Framework, is seen in Objective-C as a regular object conforming to the CIMLuaObject protocol (defined in CIMLua.h).

The CIMLuaObject protocol defines a class method luaClassName that return the Lua class name of this object (different from the Objective-C classname as returned by NSStringFromClass); it also specifies that a Lua object supports the Objective-C subscripting syntax, at the instance and class level.

Example:

id<CIMLuaObject> aLuaobject = luaContext ["anObjectVarName"];

// print the Lua class name for this object
NSLog(@"Lua class name: %@", aLuaobject.class.luaClassName);

// Use the subscripting syntax to access objects fields
NSNumber* level = aLuaobject [@"level"];
aLuaobject [@"level"] = @([level integerValue] + 1);

A Lua object is always attached to its original Lua Context. This means that calling a method on this object from Objective-C will always execute this method in the correct Lua Context.

Post a Comment