Clay Allsopp

Mixing Objective-C and Ruby

Aug 24, 2012

I've written a lot about RubyMotion, but one aspect I haven't touched on is how you can mix-and-match Objective-C and Ruby. That means you can use Objective-C code in RubyMotion projects and use Ruby code in traditional Objective-C apps. That sounds like black-magic, so let's walk through some quick examples.

Objective-C in Ruby

Most iOS developers have a deep backlog of existing code, and porting it to RubyMotion by hand is a pain. Luckily, we can just add the compiled Objective-C to our shiny new RubyMotion apps.

Imagine we have a CYAlert class we want to use in our RubyMotion app; it's pretty short and looks something like this:

// CYAlert.h
#import <UIKit/UIKit.h>

@interface CYAlert : NSObject

+ (void)show;

@end
// CYAlert.m
#import "CYAlert.h"

@implementation CYAlert

+ (void)show {
    UIAlertView *alert = [[UIAlertView alloc] init];
    alert.title = @"This is Objective-C";
    alert.message = @"Mixing and matching!";
    [alert addButtonWithTitle:@"OK"];
    [alert show];
    [alert release];
}

@end

To use this in our RubyMotion app, we just put both files inside a folder (such as ./vendor/CYAlert) and tell RubyMotion to also build that folder using vendor_project in the Rakefile:

Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.name = 'MixingExample'

  # ./vendor/CYAlert contains CYAlert.h and CYAlert.m
  app.vendor_project('vendor/CYAlert', :static)
end

Now in our AppDelegate, we can use our CYAlert as expected:

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    CYAlert.show
    true
  end
end

Pretty neat, huh? RubyMotion knows how to build two types of Objective-C codebases: :static as shown above and :xcode, which is exactly what it sounds like. You can read more about the options for both in the RubyMotion docs.

CocoaPods

Since RubyMotion can build any old Xcode project, using the existing CocoaPods system doesn't require a huge leap of imagination. The motion-cocoapods gem adds a nice DSL to automate the process in your Rakefile, like so:

...
require 'motion-cocoapods'

Motion::Project::App.setup do |app|
  # ...
  app.pods do
    pod 'JSONKit'
  end
end

Ruby in Objective-C

Compiling to a common bytecode is a two-way street: we can also bundle Ruby classes into Objective-C projects. That sounds wild, right? Imagine we re-implemented our CYAlert in Ruby, like so:

# ./app/CYAlert.rb

class CYAlert
  def self.show
    alert = UIAlertView.alloc.init
    alert.title = "This is Ruby"
    alert.message = "Mixing and matching!"
    alert.addButtonWithTitle "OK"
    alert.show
  end
end

If we run rake static, this will compile our project into a static library at ./build/<app name>-universal.a. We then add this to our Xcode project with the following steps:

  1. Add static library to project in library
  2. Link against additional required libraries linker
  3. Initalize the RubyMotion runtime
// main.m

int main(int argc, char *argv[])
{
  @autoreleasepool {
      void RubyMotionInit(int, char **);
      RubyMotionInit(argc, argv);
      ...
  }
}

Now we're cooking with gas. One caveat: RubyMotion does not generate .h files for us, so we have to manually declare our classes and methods somewhere in Objective-C. It's a good idea to create a header for each class, so we could create a CYAlert.h like this:

// CYAlert.h

@interface CYAlert : NSObject
+ (void)show;
@end

@implementation CYAlert
// Empty on purpose
@end

And in any of our classes, such as the app delegate, we need only import the header and use to our heart's content:

// ExampleDelegate.m

#import "CYAlert.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...

    [CYAlert show];
    return YES;
}

So if something is painful to write in Objective-C, just write it in Ruby and easily add it to your project. Super neat stuff.

You can find the source for the above examples on Github.