Element 84 Logo

Efficient Delegation Using the Objective C Reflection API

09.13.2013

Recently I was working on a project to provide Lua bindings to the new Sprite Kit API in the upcoming iOS 7. This allows a user to write entire Sprite Kit games by creating and calling Sprite Kit objects from Lua. The underlying technique makes heavy use of the Delegation design pattern.

While delegation is a powerful technique that is used throughout iOS, it can require a lot of tedious code to map calls to the delegate. This post describes how I was able to replace a lot of boilerplate delegation methods with two methods using the reflection API of Objective C. This makes for a much cleaner implementation and a more flexible solution that can adapt to changes in the underlying Sprite Kit API without changing the bindings.

I’ll start by presenting a simple implementation of the basic binding code and then describe the problems that arise when the binding functionality is expanded. Finally, I’ll demonstrate how reflection can solve these problems. The bindings are libraries of C functions that provide factory methods to instatiate Sprite Kit objects as well as object methods that map Lua calls to these objects. A typical factory method call looks like this in Lua:

local circle = shapes.newCircle(10, 50, 50)

And an object method call looks like this:

circle:setFillColor(1,0,1,1)

The bindings themselves are implemented as libraries of C functions that are registered (bound) with the Lua runtime. The bindings for our factory and object method above look like this:

// the mappings for the library functions
static const struct luaL_Reg shapeLib_f [] = {
    {"newCircle", newCircle},
    {NULL, NULL}
};

// mappings for the circle methods
static const struct luaL_Reg circle_m [] = {
    {"setFillColor", setFillColor},
    {NULL, NULL}
};

// the registration function
int luaopen_shape_lib (lua_State *L){
    // create meta tables for our various types /////////

    // circle
    createMetatable(L, GEMINI_CIRCLE_LUA_KEY, circle_m);

    /////// finished with metatables ///////////

    // create the table for this library and popuplate it with our functions
    luaL_newlib(L, shapeLib_f);

    return 1;
}

This code implements a library that defines two new Lua methods, a factory method called createCircle and method for the circles called setFillColor. (The C functions that implement these two methods are not shown, but they simply delegate to the Sprite Kit API.) n In addition to calling object methods like setFillColor, I wanted to provide full Lua object functionality for the objects returned by my factory methods. This means they should behave like tables and allow developers to set and retrieve values on them with code like this:

circle.foo = 100
local foo = circle.fo

This is easy enough to add to our bindings. We simply need to define two special methods for our objects, __index and __newindex. These are known as meta methods in Lua parlance, as they are not called directly, rather they are invoked using the dot operator in code like that given above. We can do this by adding the additional methods to our methods array like so:

// mappings for the circle methods
static const struct luaL_Reg circle_m [] = {
    {"setFillColor", setFillColor},
    {"__index", index},
    {"__newindex", newIndex},
    {NULL, NULL}
};

The code for the new methods is given here:

// __index method for Lua objects
// provides table "getter" functionality
static int index( lua_State* L ) {
    // first check the uservalue
    lua_getuservalue( L, -2 );
    lua_pushvalue( L, -2 );

    lua_rawget( L, -2 );
    if( lua_isnoneornil( L, -1 ) != 0 ) {
        lua_pop( L, 2 );

        // check the metatable
        lua_getmetatable( L, -2 );
        lua_pushvalue( L, -2 );
        lua_rawget( L, -2 );
    }

    return 1; // tell Lua we have returned 1 value on the stack
}

static int newIndex( lua_State* L ) {
// __newindex method for Lua objects
// provides table "setter" functionality
    lua_getuservalue( L, -3 ); // table attached is attached to objects via user value
    lua_pushvalue(L, -3);
    lua_pushvalue(L,-3);
    lua_rawset( L, -3 );

    return 0; // we are returning nothing on the stack
}

These methods use a Lua table attached to our objects to store and retrieve values transparently, so our objects act like tables in Lua (in addition to the other functionality they provide). So here we are delegating these two methods to the attached Lua table, rather than to the Sprite Kit objects.

Writing bindings for Lua objects involves a lot of boiler-plate code like this. Since these two functions are fairly generic, it’s possible to use them for all the new Lua types we define.

So far, we have added the abilty to create Sprite Kit objects in Lua and call methods on them. Additionally we have given our objects the ability to behave like Lua tables, so programmers can store arbitray data (health points, etc.) in them. To really take advantage of the delegation pattern, however, I want any properties of the underlying Sprite Kit objects to be readable/settable through this mechanism. So, if the Lua code says this:

circle.x = 100

Then the underlying Sprite Kit object would have it’s x property set to 100. Let me be clear here, if the property being get or set in Lua is a property of the underlying Sprite Kit object, then the value should be stored/retrieved from that object, not from the attached Lua table. This makes it easy for the Lua programmer to use the dot notation to set x-positition, y-position, or any other single valued property. Multi-component properties like color are still set using object methods like setColor. n I can get this behavior by modifying my index and newIndex methods to delegate any request for known properties to the underlying Sprite Kit object.

static int index(lua_State *L){
    int rval = 0;
    __unsafe_unretained GemCircle **circle = (__unsafe_unretained GemCircle **)luaL_checkudata(L, 1, GEMINI_CIRCLE_LUA_KEY);
    if (circle != NULL) {
        if (lua_isstring(L, -1)) {
            const char *key = lua_tostring(L, -1);
                if (strcmp("x", key) == 0) {
                    CGFloat x = (*circle).x;
                    lua_pushnumber(L, w);
                    rval = 1;
                } else {
                    rval = genericIndex(L);
                }
        }
    }

    return rval;
}

static int newIndex (lua_State *L){
    int rval = 0;
    __unsafe_unretained GemCircle **circle = (__unsafe_unretained GemCircle **)luaL_checkudata(L, 1, GEMINI_CIRCLE_LUA_KEY);

    if (circle != NULL) {
        if (lua_isstring(L, 2)) {
            const char *key = lua_tostring(L, 2);
            if (strcmp("x", key) == 0) {
                CGFloat x = luaL_checknumber(L, 3);
                (*circle).x = x;
                rval = 0;
            } else {

                rval = genericNewIndex(L, circle);
            }

        }

    }

    return rval;
}

Here we have delegated calls to get or set the x property of our circles to our Objective C object. We also need to add a strcmp for every property we wanted to delegate this way. As Sprite Kit objects have a lot of properties, this gets unwieldy quickly. n Also, this has to be done for every object type since they will have different properties. We can factor out code for any common properties and delegate to that, but there is still a lot of code to write. Fortunately, Objective C provides a better way.

Unlike C++, Objective C provides runtime method binding. Among other niceties, this enables us to write code that checks to see if an object supports a particular interface at runtime. In other words, we can check to see if an object responds to certain selectors. Even better, we can then invoke those selectors dynamically.

So how does this help? Well, now instead of having to implement custom code to provide bindings for every property we know/care about, we can test an object at runtime to see if it has a given property and delegate to that if so. If not, we fall back on our generic Lua table store. So we don’t need to know anything about the properties of our underlying objects, we just query them at runtime as necessary. As an added benefit, if the API for the underlying objects changes, we don’t need to add or remove code to handle new or removed properties.

We can now write generic index and newIndex methods that will work for all of our objects. The code is as follows:

// generic index method for userdata types
int genericIndex(lua_State *L){
    // first check to see if the delegate object will accept the call
    __unsafe_unretained GemObject **go = (__unsafe_unretained GemObject **)lua_touserdata(L, 1);
    NSObject *delegate = (*go).delegate;

    const char *attr = luaL_checkstring(L, 2);

    // check to see if the delgate object can handle the call
    NSString *methodName = [NSString stringWithFormat:@"%s", attr];

    SEL selector = NSSelectorFromString(methodName);
    if ([delegate respondsToSelector:selector]) {
        // use the delegate object to handle the call
        NSMethodSignature *sig = [delegate methodSignatureForSelector:selector];
        const char *returnType = [sig methodReturnType];
        NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig];
        [invoke setTarget:delegate];

        [invoke setSelector:selector];
        [invoke invoke];

        if (strcmp("f", returnType) == 0){
            float fVal;
            [invoke getReturnValue:&fVal];
            lua_pushnumber(L, fVal);

        } else if (strcmp("i", returnType) == 0) {
            int iVal;
            [invoke getReturnValue:&iVal];
            lua_pushinteger(L, iVal);
        } else if (strcmp("u", returnType) == 0) {
            unsigned int uVal;
            [invoke getReturnValue:&uVal];
            lua_pushunsigned(L, uVal);
        } else if (strcmp("d", returnType) == 0) {
            double dVal;
            [invoke getReturnValue:&dVal];
            lua_pushnumber(L, dVal);
        } else {
            // everything else is treated as a string
            NSString *sVal;
            [invoke getReturnValue:&sVal];
            lua_pushstring(L, [sVal cStringUsingEncoding:[NSString defaultCStringEncoding]]);
        }

    } else {

        // first check the uservalue
        lua_getuservalue( L, -2 );
        lua_pushvalue( L, -2 );

        lua_rawget( L, -2 );
        if( lua_isnoneornil( L, -1 ) == 0 ) {
            return 1;
        }

        lua_pop( L, 2 );

        // second check the metatable
        lua_getmetatable( L, -2 );
        lua_pushvalue( L, -2 );
        lua_rawget( L, -2 );

    }

    // nil or otherwise, we return here
    return 1;

}

// generic new index method, i.e., obj.something = some_value
// only support primitive types (ints, float, char *, etc.) for some_value
    int genericNewIndex(lua_State *L) {
    __unsafe_unretained GemObject **go = (__unsafe_unretained GemObject **)lua_touserdata(L, 1);
    NSObject *delegate = (*go).delegate;

    const char *attr = luaL_checkstring(L, 2);
    NSString *attrStr = [NSString stringWithFormat:@"%s", attr];
    // check to see if the delgate object can handle the call
    NSString *firstCapChar = [[attrStr substringToIndex:1] capitalizedString];
    NSString *cappedString = [attrStr stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:firstCapChar];
    NSString *methodName = [NSString stringWithFormat:@"set%@:", cappedString];
    SEL selector = NSSelectorFromString(methodName);
    if ([delegate respondsToSelector:selector]) {
        // use the delegate object to handle the call
        NSMethodSignature *sig = [delegate methodSignatureForSelector:selector];
        const char *argType = [sig getArgumentTypeAtIndex:2];
        NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig];
        [invoke setTarget:delegate];
        NSString *sVal;
        char *cVal;
        double dVal;
        float fVal;
        int iVal;
        unsigned int uVal;

        if (strcmp("*", argType) == 0) {
            // char * string
            cVal = (char *)luaL_checkstring(L, 3);
            [invoke setArgument:&cVal atIndex:2];
        } else if (strcmp("f", argType) == 0){
            fVal = luaL_checknumber(L, 3);
            [invoke setArgument:&fVal atIndex:2];
        } else if (strcmp("i", argType) == 0) {
            iVal = luaL_checkinteger(L, 3);
            [invoke setArgument:&iVal atIndex:2];
        } else if (strcmp("u", argType) == 0) {
            uVal = luaL_checkunsigned(L, 3);
            [invoke setArgument:&uVal atIndex:2];
        } else if (strcmp("d", argType) == 0) {
            dVal = luaL_checknumber(L, 3);
            [invoke setArgument:&dVal atIndex:2];
        } else {
            // everything else is treated as a string
            cVal = (char *)luaL_checkstring(L, 3);
            sVal = [NSString stringWithFormat:@"%s", cVal];
            [invoke setArgument:&sVal atIndex:2];
        }

        [invoke setSelector:selector];
        [invoke invoke];

    } else {
        // use the attache Lua table
        // this function gets called with the table on the bottom of the stack,
        // the index to assign to next, and the value to be assigned on top
        lua_getuservalue( L, -3 ); // table attached is attached to objects via user value
        lua_pushvalue(L, -3);
        lua_pushvalue(L,-3);
        lua_rawset( L, -3 );

    }

    return 0;
}

Each methods checks to see if the underlying objects provides the appropriate getter or setter by consructing the name of the selector from the requested property and calling respondsToSelector. If the underlying object provides the getter or setter, it is invoked using an NSInvocation, otherwise the attached Lua table is used. For getters, the return type of the method signature is used to determine how to pass the value back to Lua. For setters, the argument type of the method signature is used to determine what type to use when pulling the argument off the Lua stack.

While this code is somewhat more complicated than the earlier delegation code, it eliminates a huge amount boiler-plate. The use of reflection does make the delegation code run somewhat slower, which may be a concern for some applications. In this case, Lua code is typically not executed every frame, so speed is less important than a smaller, easier to maintain codebase.

Conclusion

Throughout this post I have talked about Lua bindings which involve a lot of boiler-plate code and are a great example of the Delegation pattern. But what if you aren’t writing the next great Lua library? Can you still make use of this technique? In a word, yes.

Delegation is a highly useful pattern and it is can be found throughout the iOS APIs. Any time you find yourself writing a lot of boiler-plate code while working with delegates, it may be useful to consider replacing hard coded delegation with a generic implementation that uses reflection. You may be able to shrink your codebase while improving its flexibility, a true win-win.