Can Ruby, Python and Objective-C Co-exist in a Single Application?

In short, Yes. But not without some pain.

You can grab an example of the Python and Ruby bridges working together in a Cocoa application from either this downloadable zip or from this Subversion repository (in case something actually changes).

It works. Sort of. Ironically, this likely would have worked better under the pre-BridgeSupport versions of RubyCocoa and PyObjC.

Specifically, RubyCocoa and PyObjC both assume that nothing else might have loaded the dynamic libraries that are automatically generated by the gen_bridge_metadata script. So, either bridge will quite happily attempt to load /System/Library/Frameworks/Foundation.framework/Resources/BridgeSupport/Foundation.dylib and then barf mightily when the other bridge has already loaded the same dylib.

The example is rife with silliness related to catching the resulting exceptions and ignoring them. Worse, the fallout is such that from Foundation import * doesn’t actually cause the Objective-C classes to be defined within the importing module.

There is a back door — objc.lookUpClass() — but this is yet further evidence that, at this time, mixing these two languages in a single Cocoa application is not anything more than a silly hacque (as the SVN repository subdir indicates).

What does it do?

Not much, really.

  • Start with RubyCocoa Application Template (because I can deal with brokeness in Python, I wanted to start with something working in Ruby)
  • NSApp Delegate written in Obj-C
  • Finish loading hooks used to bootstrap PyObjC/Python (with gross exception ignoring goofiness related to the dylib)
  • Bind a table view to an array of dicts where each dict has the key “name” leading to a string value bound through array controller.
  • Array controller bound through app delegate method.
  • App delegate returns array of dictionaries by calling python based NSObject subclass.
  • Python based NSObject subclass composes an array of dictionaries (all python) from a combinatio of Python strings and Ruby strings by calling an instance of a Ruby based subclass of NSObject.

Very little code. Lots of moving parts. Some gears grinding. Maybe even a gear tooth or four missing. Enjoy.



11 Responses to “Can Ruby, Python and Objective-C Co-exist in a Single Application?”

  1. Jesper says:

    It burns!!! Burns!!!!! 😉

  2. psu says:

    You should totally add perl too.

  3. Ahruman says:

    So, er, should I be seeing an empty table and the following log messages?

    Traceback (most recent call last):
    File “/Users/jayton/Downloads/PyRu/build/Release/PyRu.app/Contents/Resources/main.py”, line 10, in
    import objc
    ImportError: No module named objc
    2007-11-25 14:56:18.660 PyRu[383:10b] /Users/jayton/Downloads/PyRu/AppDelegate.m:48 main() PyRun_SimpleFile failed with file ‘/Users/jayton/Downloads/PyRu/build/Release/PyRu.app/Contents/Resources/main.py’. See console for errors.

  4. Ahruman says:

    Oh, wait, old version of Python.framework in /Library. (This could be a bit of a problem for a real user, though…)

  5. Clint says:

    Hey, I’ve actually been looking around for the past week for a objc – python combination example and have yet to find one. This works enough for me, though i don’t care for ruby at the moment. Thanks!

  6. Jason says:

    This is a major bummer, as it would seem to rule out writing any sort of loadable bundle using RubyCocoa or PyObjC, unless you could be sure one and only one bridge would be loaded into the application. So long RubyCocoa/PyObjC PreferencePanes, Mail plugin bundles, etc. Many useful Mail.app plugin bundles on Tiger used PyObjC, for example. Hope this is high on the list of stuff to be fixed in the next update!

  7. red says:

    WOW… you just saved me. I’ve been hunting around for hours trying to find how to actually implement the bi-directionality of the PyObjC bridge. Specifically — how can I have an Objective C controller referencing a Python model class. This totally should be possible, right?

    The problem, is that every single PyObjC example has basically done the *entire* project in Python — or they had some nasty setup scripting and bundle plugin loading crud. This can be a pain in the butt — I’d like to use Objective-C for certain controller areas and leave Python to the data crunching.

    Your example seems to work (on the python side) for dynamically loading the python class into Objective C syntax.

    Could you please explain some of the details of your AppDelegate.m a little more (in the PyRu sample)? I’m a total noob and the learning curve is steep…. I’m specifically wondering if lines 11-15 are needed for working with the python sublcass. Or — at line 57, Is all that is needed is the NSClassFromString call? Or is it a combination of both.

    Once again, since this is the first actual example I found of Objective-C utilizing a Python class — I’m sure there are others out there interested. THANKS.

  8. red says:

    Okay — I got if figured out (for those watching these comments).
    To explain to us newbies to Objective-C: Bob is using the @class directive to forward declare the class. The next line is adding a category to NSObject classes so that later on the compiler won’t freak.

    Now — one important thing to note is that line 57 will compile but will NOT work if you don’t edit the “main.py” file included when you created the XCode project. Specifically you need to make sure to import your python class declarations *BEFORE* the AppHelper line launches everything. In the standard template you’ll see the import pyxxxAppDelegate line.

    So far so good — now if I could only find a way to pass an Objective-C (int) to a python instance method.

  9. red says:

    Okay — I got if figured out (for those watching these comments).
    To explain to us newbies to Objective-C: Bob is using the @class directive to forward declare the class. The next line is adding a category to NSObject classes so that later on the compiler won’t freak.

    Now — one important thing to note is that line 57 will compile but will NOT work if you don’t edit the “main.py” file included when you created the XCode project. Specifically you need to make sure to import your python class declarations *BEFORE* the AppHelper line launches everything. In the standard template you’ll see the import pyxxxAppDelegate line.

    So far so good — now if I could only find a way to pass an Objective-C (int) to a python instance method.

  10. red says:

    WOOOHOOOO!
    Okay, for those of you who internet searched to get here (’cause you were suffering the same level of documentation void that I have been suffering) — I have some explanation of 2 days of struggling and trial and (lots of) error. PyObjC really does bridge both ways, even if the examples fail to show this (which they do), you can call Python from within Objective-C code.

    Repeating that: Yes, you can indeed instantiate a python class object (as demo’d by Bob in his attached source code) and call all sorts of instance methods on that object.

    Example 1: NSStrings across the bridge are easy if you declare a python function: def myPyStringFun_(self, astring): print "The passed type is " + str(type(aNumber)) you will find that the Objective-C call [mypythonobject myPyStringFun: myNSString] works just fine and the you’ll see: The passed type is

    Example 2: we run into a snag trying to pass an int across the bridge
    However, if you try to do something like
    def myPyIntegerFun_(self, aNumber):
    print "The passed type is " + str(type(aNumber))

    you’ll be surprised like me to find that
    int myInteger = 42;
    [mypythonobject myPyIntegerFun: myInteger];

    completely fails. The debugger will just exit and freak out with bad address warnings, etc — it won’t even enter the first line of your python function to tell you what was passed into it.

    Example 3: Successfully getting an int across the PyObjC bridge
    The bridge, as described in the documents works bi-directionally (although bidirectional examples are very difficult to find) using the types on Objective-C end. In other words if you have
    def myPyIntegerFun_(self, aNumber):
    print "The passed type is " + str(type(aNumber))

    and you setup your Objective-C with:
    NSNumber * tempint = [[NSNumber alloc] initWithInt:42;
    tempString = [gamerObject outputAsBinary:tempint];

    you will be rewarded with a happy Python and a log that reads
    The passed type is

    In summary:
    (a) From Objective-C use an NSNumber in your method call to receive an int on the python end of things (or float, I assume).
    (b) This was done on Leopard with XCode 3 and Interface Builder 3……
    (c) By using Objective-C for the controller aspect, it makes the interface builder hooks and/or bindings straightforward
    (d) And more importantly…. this should help keep the Python code clean and (mostly) pure python. I have a bunch of little python tools that I want to throw front ends onto —I’d hate to re-code them with random objc.ivar() stuff littered all over the place — thanks to Bob’s example above and a little trial and lots of error, it’s easy enough to instantiate python objects from the Objective-C side and feed those objects the values they need.

    Hope that helps some more of you Python Objective-C XCode people

  11. sbwoodside says:

    Thanks for posting this. I’m doing the same thing between MacRuby ruby and Objective-C. I figured it out with clues from here:

Leave a Reply

Line and paragraph breaks automatic.
XHTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>